From 4c2aa3422f949f5e16d1b1d132dfb4a646673d65 Mon Sep 17 00:00:00 2001 From: Jamie V Date: Fri, 7 Nov 2025 13:46:34 -0800 Subject: [PATCH 01/22] wip --- package.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 99529ef..2a28496 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,8 @@ "name": "openmct-mcws", "version": "v5.4.0-rc3", "description": "Open MCT for MCWS", + "main": "dist/openmct-mcws.js", + "type": "module", "devDependencies": { "@babel/eslint-parser": "7.26.8", "@braintree/sanitize-url": "6.0.4", @@ -40,7 +42,6 @@ "mini-css-extract-plugin": "2.7.6", "moment": "2.30.1", "node-bourbon": "^4.2.3", - "openmct": "github:nasa/openmct#omm-r5.4.0-rc4", "prettier": "3.4.2", "printj": "1.3.1", "raw-loader": "^0.5.1", @@ -57,6 +58,9 @@ "webpack-dev-server": "5.0.2", "webpack-merge": "5.10.0" }, + "peerDependencies": { + "openmct": "^4.1.0" + }, "scripts": { "clean": "npm cache clean --force;rm -rf ./dist ./node_modules ./target ./package-lock.json", "start": "npx webpack serve --config ./.webpack/webpack.dev.js", From 6af15bcc1ecefbe155866a3042e22d6ff9ea18d1 Mon Sep 17 00:00:00 2001 From: Jamie V Date: Thu, 20 Nov 2025 11:57:25 -0800 Subject: [PATCH 02/22] es6 conversion and pluginization --- .webpack/webpack.common.js | 74 +- .webpack/webpack.dev.js | 19 +- .webpack/webpack.prod.js | 6 +- README.md | 2 +- .../default/assets/installCommonJsPlugin.js | 24 + instances/default/assets/installEs6Plugin.js | 17 + instances/default/assets/load-umd.js | 29 + .../assets/mct-bootstrap-plugin/an-asset.txt | 1 + .../assets/mct-bootstrap-plugin/package.json | 12 + .../assets/mct-bootstrap-plugin/plugin.js | 48 + .../assets/openmct-configuration-schema.json | 111 +++ instances/default/index.html | 30 + instances/default/instance.yaml | 74 ++ instances/default/package.json | 17 + .../assets/installBuiltinPlugin.js | 9 + .../assets/installCommonJsPlugin.js | 36 + .../development/assets/installEs6Plugin.js | 24 + instances/development/assets/load-umd.js | 29 + .../assets/mct-bootstrap-plugin/an-asset.txt | 1 + .../assets/mct-bootstrap-plugin/package.json | 12 + .../assets/mct-bootstrap-plugin/plugin.js | 48 + .../development/assets/mct-builder-core.d.ts | 27 + .../development/assets/mct-builder-core.js | 114 +++ .../assets/openmct-configuration-schema.json | 111 +++ instances/development/index.html | 34 + instances/development/instance.yaml | 124 +++ instances/development/package.json | 18 + loader.js | 165 ---- package.json | 15 +- plugin.js | 277 ++++++ recipes/default.yaml | 52 ++ recipes/development.yaml | 56 ++ serve-instance.js | 173 ++++ src/AMMOSPlugins.js | 155 ---- .../ImageExport/ImageExportModifier.js | 4 +- .../importWithDatasetsModifier.js | 2 +- ...warnMultipleDatasetsOnDuplicateModifier.js | 2 +- .../warnMultipleDatasetsOnImportModifier.js | 4 +- src/actionModifiers/plugin.js | 8 +- src/alarmsView/AlarmsAutoclearViewProvider.js | 2 +- src/alarmsView/AlarmsTable.js | 4 +- src/alarmsView/AlarmsViewProvider.js | 2 +- src/alarmsView/plugin.js | 8 +- src/channelLimits/plugin.js | 2 +- src/channelLimits/pluginSpec.js | 2 +- .../channelTablePlugin/ChannelTable.js | 8 +- .../ChannelTableFormatViewProvider.js | 2 +- .../ChannelTableViewProvider.js | 4 +- .../channelTablePlugin/ObjectNameColumn.js | 48 +- src/channelTable/channelTablePlugin/plugin.js | 8 +- .../ChannelTableSetCompositionPolicy.js | 2 +- .../ChannelTableSetView.js | 2 +- .../ChannelTableSetViewProvider.js | 2 +- .../channelTableSetPlugin/plugin.js | 6 +- src/clearDataIndicator/plugin.js | 2 +- .../CommandEventsViewProvider.js | 2 +- src/constants.js | 412 +++++---- src/containerView/FolderGridViewProvider.js | 2 +- src/containerView/FolderListViewProvider.js | 2 +- src/containerView/plugin.js | 6 +- .../UrlField/UrlFieldFormController.js | 2 +- src/customForms/plugin.js | 2 +- src/dictionaryView/dictionaryViewProvider.js | 2 +- src/dictionaryView/dictionaryViewTable.js | 2 +- src/dictionaryView/plugin.js | 2 +- src/evrView/EVRTable.js | 2 +- .../EVRViewLevelsConfigurationViewProvider.js | 2 +- src/evrView/EVRViewProvider.js | 4 +- src/evrView/plugin.js | 6 +- src/exportDataAction/ExportDataAction.js | 6 +- src/exportDataAction/plugin.js | 2 +- src/formats/JSONStringFormat.js | 31 +- src/formats/LMSTFormat.js | 149 ++- src/formats/MSLSOLFormat.js | 82 +- src/formats/SCLKFloat64Format.js | 36 +- src/formats/UTCDayOfYearFormat.js | 2 +- src/formats/UTCFormat.js | 2 +- src/formats/plugin.js | 45 +- .../FrameEventFilterViewProvider.js | 2 +- .../components/frameAccountability.js | 410 ++++----- .../components/frameAccountabilityNode.js | 102 +- .../frameAccountabilityViewProvider.js | 6 +- src/frameaccountability/plugin.js | 4 +- src/framesummary/FrameWatchColumn.js | 56 +- .../FrameWatchConfigurationViewProvider.js | 4 +- .../FrameWatchTableConfiguration.js | 4 +- src/framesummary/FrameWatchViewProvider.js | 6 +- src/framesummary/config.js | 2 +- .../encodingwatch/EncodingWatchRow.js | 6 +- src/framesummary/plugin.js | 6 +- src/globalFilters/plugin.js | 2 +- src/historical/HistoricalProvider.js | 869 +++++++++--------- src/historical/HistoricalProviderSpec.js | 2 +- src/historical/plugin.js | 38 +- src/lib/eventHelpers.js | 125 +-- src/lib/extend.js | 62 +- src/link/plugin.js | 84 +- src/mcwsIndicator/MCWSIndicator.vue | 2 +- src/mcwsIndicator/MCWSIndicatorSpec.js | 4 +- src/mcwsIndicator/plugin.js | 2 +- .../MessageStreamProcessor.js | 4 +- src/messageStreamProcessor/plugin.js | 26 +- src/messagesView/MessagesViewProvider.js | 2 +- src/metadataAction/metadataAction.js | 2 +- src/metadataAction/plugin.js | 2 +- .../indicator/historicalSessionIndicator.vue | 4 +- src/multipleHistoricalSessions/plugin.js | 6 +- .../historicalSessionSelector.vue | 2 +- src/packetQuery/MCWSURLBuilder.js | 53 +- src/packetQuery/PacketQueryViewProvider.js | 2 +- .../components/PacketQueryView.vue | 2 +- src/packetQuery/plugin.js | 2 +- src/packetSummary/PacketSummaryTable.js | 4 +- .../PacketSummaryViewProvider.js | 2 +- src/packetSummary/plugin.js | 2 +- .../BaseMCWSPersistenceProvider.js | 4 +- src/persistence/MCWSPersistenceProvider.js | 4 +- src/persistence/MCWSUserContainerProvider.js | 4 +- .../oldPersistenceFolderInterceptor.js | 2 +- src/persistence/plugin.js | 10 +- .../test/MCWSNamespaceModelProviderSpec.js | 6 +- .../test/MCWSNamespaceServiceSpec.js | 6 +- .../test/MCWSPersistenceProviderSpec.js | 4 +- src/product-status/DataProductCell.js | 2 +- .../DataProductInspectorViewProvider.js | 2 +- src/product-status/DataProductRowSpec.js | 2 +- src/product-status/DataProductTable.js | 12 +- .../MCWSAlarmMessageStreamProvider.js | 2 +- src/realtime/MCWSChannelStreamProvider.js | 2 +- src/realtime/MCWSCommandStreamProvider.js | 2 +- src/realtime/MCWSDataProductStreamProvider.js | 2 +- src/realtime/MCWSEVRLevelStreamProvider.js | 2 +- src/realtime/MCWSEVRStreamProvider.js | 2 +- src/realtime/MCWSFrameEventStreamProvider.js | 2 +- .../MCWSFrameSummaryStreamProvider.js | 2 +- src/realtime/MCWSMessageStreamProvider.js | 2 +- .../MCWSPacketSummaryEventProvider.js | 2 +- src/realtime/MCWSStreamProvider.js | 8 +- src/realtime/MCWSStreamWorker.js | 14 +- src/realtime/plugin.js | 22 +- src/realtimeIndicator/plugin.js | 2 +- .../components/RealtimeSessionIndicator.vue | 2 +- .../components/RealtimeSessionSelector.vue | 2 +- src/realtimeSessions/plugin.js | 2 +- src/services/dataset/ChannelDictionary.js | 216 +++-- src/services/dataset/Dataset.js | 210 +++-- src/services/dataset/DatasetCache.js | 4 +- src/services/dataset/EVRDictionary.js | 168 ++-- src/services/filtering/FilterService.js | 2 +- .../globalStaleness/globalStaleness.js | 2 +- src/services/identity/MCWSIdentityProvider.js | 4 +- .../identity/MCWSIdentityProviderSpec.js | 4 +- src/services/mcws/DataTableMIO.js | 2 +- src/services/mcws/MCWSClient.js | 14 + src/services/mcws/MIO.js | 4 +- src/services/mcws/NamespaceMIO.js | 6 +- src/services/mcws/OpaqueFileMIO.js | 2 +- src/services/mcws/mcws.js | 8 +- .../session/SessionLocalStorageHandler.js | 81 +- src/services/session/SessionService.js | 10 +- .../test/service/SessionServiceSpec.js | 4 +- src/tables/VistaTableConfigurationProvider.js | 2 +- src/taxonomy/ChannelAlarmPlugin.js | 316 ++++--- src/taxonomy/DatasetCompositionProvider.js | 30 +- src/taxonomy/EVRHighlightProvider.js | 49 +- .../FrameEventFilterObjectProvider.js | 26 +- src/taxonomy/TaxonomyCompositionProvider.js | 24 +- src/taxonomy/TaxonomyObjectProvider.js | 64 +- src/taxonomy/injectEVRStylesheet.js | 106 ++- src/taxonomy/plugin.js | 122 ++- src/time/ERTTimeSystem.js | 8 +- src/time/LMSTTimeSystem.js | 24 +- src/time/MSLSolTimeSystem.js | 24 +- src/time/SCETTimeSystem.js | 8 +- src/time/SCLKTimeSystem.js | 8 +- src/types/AlarmMessageStreamType.js | 18 +- src/types/ChannelAlarmsType.js | 58 +- src/types/ChannelGroupType.js | 68 +- src/types/ChannelSourceType.js | 58 +- src/types/ChannelType.js | 346 ++++--- src/types/CommandEventsType.js | 398 ++++---- src/types/DataProductsType.js | 265 +++--- src/types/DatasetType.js | 367 ++++---- src/types/DictionarySourceType.js | 60 +- src/types/DictionaryType.js | 75 +- src/types/EVRModuleType.js | 170 ++-- src/types/EVRSourceType.js | 125 +-- src/types/EVRType.js | 119 ++- src/types/FrameEventFilterType.js | 32 +- src/types/FrameEventType.js | 157 ++-- src/types/FrameSummaryType.js | 178 ++-- src/types/HeaderChannelSourceType.js | 2 +- src/types/HeaderChannelType.js | 14 +- src/types/MessageType.js | 142 ++- src/types/PacketSummaryEventsType.js | 188 ++-- src/types/PacketsType.js | 143 +-- src/types/VISTAType.js | 68 +- src/types/plugin.js | 4 +- src/types/types.js | 204 ++-- src/venues/Venue.js | 4 +- src/venues/VenueService.js | 8 +- src/venues/VenueSpec.js | 4 +- .../HistoricalSessionSelectorComponent.vue | 2 +- src/venues/plugin.js | 2 +- 204 files changed, 5509 insertions(+), 4236 deletions(-) create mode 100644 instances/default/assets/installCommonJsPlugin.js create mode 100644 instances/default/assets/installEs6Plugin.js create mode 100644 instances/default/assets/load-umd.js create mode 100644 instances/default/assets/mct-bootstrap-plugin/an-asset.txt create mode 100644 instances/default/assets/mct-bootstrap-plugin/package.json create mode 100644 instances/default/assets/mct-bootstrap-plugin/plugin.js create mode 100644 instances/default/assets/openmct-configuration-schema.json create mode 100644 instances/default/index.html create mode 100644 instances/default/instance.yaml create mode 100644 instances/default/package.json create mode 100644 instances/development/assets/installBuiltinPlugin.js create mode 100644 instances/development/assets/installCommonJsPlugin.js create mode 100644 instances/development/assets/installEs6Plugin.js create mode 100644 instances/development/assets/load-umd.js create mode 100644 instances/development/assets/mct-bootstrap-plugin/an-asset.txt create mode 100644 instances/development/assets/mct-bootstrap-plugin/package.json create mode 100644 instances/development/assets/mct-bootstrap-plugin/plugin.js create mode 100644 instances/development/assets/mct-builder-core.d.ts create mode 100644 instances/development/assets/mct-builder-core.js create mode 100644 instances/development/assets/openmct-configuration-schema.json create mode 100644 instances/development/index.html create mode 100644 instances/development/instance.yaml create mode 100644 instances/development/package.json delete mode 100644 loader.js create mode 100644 plugin.js create mode 100644 recipes/default.yaml create mode 100644 recipes/development.yaml create mode 100755 serve-instance.js delete mode 100644 src/AMMOSPlugins.js diff --git a/.webpack/webpack.common.js b/.webpack/webpack.common.js index 925fcee..e70d0a4 100644 --- a/.webpack/webpack.common.js +++ b/.webpack/webpack.common.js @@ -1,32 +1,36 @@ -const path = require('path'); -const packageDefinition = require('../package.json'); -const webpack = require('webpack'); -const MiniCssExtractPlugin = require('mini-css-extract-plugin'); +import { execSync } from 'node:child_process'; +import { fileURLToPath } from 'url'; +import { dirname, join } from 'path'; +import pkg from '../package.json' with { type: 'json' }; -const { VueLoaderPlugin } = require('vue-loader'); +import webpack from 'webpack'; +import MiniCssExtractPlugin from 'mini-css-extract-plugin'; + +import { VueLoaderPlugin } from 'vue-loader'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); let gitRevision = 'error-retrieving-revision'; let gitBranch = 'error-retrieving-branch'; try { - gitRevision = require('child_process').execSync('git rev-parse HEAD').toString().trim(); - gitBranch = require('child_process') - .execSync('git rev-parse --abbrev-ref HEAD') - .toString() - .trim(); + gitRevision = execSync('git rev-parse HEAD').toString().trim(); + gitBranch = execSync('git rev-parse --abbrev-ref HEAD').toString().trim(); } catch (err) { console.warn('Error retreiving git info', err); } /** @type {import('webpack').Configuration} */ const config = { - context: path.join(__dirname, '..'), + context: join(__dirname, '..'), entry: { - 'openmct-mcws': './loader.js' + 'openmct-mcws-plugin': './plugin.js' + }, + experiments: { + outputModule: true, // Enables the feature }, output: { library: { - name: 'openmctMCWS', - type: 'umd' + type: 'module' }, filename: '[name].js', hashFunction: 'xxhash64', @@ -38,54 +42,54 @@ const config = { * Open MCT Source Paths * TODO FIXME these rely on openmct core source paths becase we extend core code directly */ - '@': path.join(__dirname, '..', 'node_modules/openmct/src'), - objectUtils: path.join( + '@': join(__dirname, '..', 'node_modules/openmct/src'), + objectUtils: join( __dirname, '..', 'node_modules/openmct/src/api/objects/object-utils.js' ), - utils: path.join(__dirname, '..', 'node_modules/openmct/src/utils'), - 'openmct.views.FolderGridViewComponent': path.join( + utils: join(__dirname, '..', 'node_modules/openmct/src/utils'), + 'openmct.views.FolderGridViewComponent': join( __dirname, '..', 'node_modules/openmct/src/plugins/folderView/components/GridView.vue' ), - 'openmct.views.FolderListViewComponent': path.join( + 'openmct.views.FolderListViewComponent': join( __dirname, '..', 'node_modules/openmct/src/plugins/folderView/components/ListView.vue' ), - 'openmct.tables.TelemetryTable': path.join( + 'openmct.tables.TelemetryTable': join( __dirname, '..', 'node_modules/openmct/src/plugins/telemetryTable/TelemetryTable.js' ), - 'openmct.tables.TelemetryTableColumn': path.join( + 'openmct.tables.TelemetryTableColumn': join( __dirname, '..', 'node_modules/openmct/src/plugins/telemetryTable/TelemetryTableColumn.js' ), - 'openmct.tables.TelemetryTableRow': path.join( + 'openmct.tables.TelemetryTableRow': join( __dirname, '..', 'node_modules/openmct/src/plugins/telemetryTable/TelemetryTableRow.js' ), - 'openmct.tables.TelemetryTableConfiguration': path.join( + 'openmct.tables.TelemetryTableConfiguration': join( __dirname, '..', 'node_modules/openmct/src/plugins/telemetryTable/TelemetryTableConfiguration.js' ), - 'openmct.tables.collections.TableRowCollection': path.join( + 'openmct.tables.collections.TableRowCollection': join( __dirname, '..', 'node_modules/openmct/src/plugins/telemetryTable/collections/TableRowCollection.js' ), - 'openmct.tables.components.Table': path.join( + 'openmct.tables.components.Table': join( __dirname, '..', 'node_modules/openmct/src/plugins/telemetryTable/components/TableComponent.vue' ), - 'openmct.tables.components.TableConfiguration': path.join( + 'openmct.tables.components.TableConfiguration': join( __dirname, '..', 'node_modules/openmct/src/plugins/telemetryTable/components/TableConfiguration.vue' @@ -93,24 +97,24 @@ const config = { /** * Globals **/ - openmct: path.join(__dirname, '..', 'node_modules/openmct/dist/openmct.js'), + openmct: join(__dirname, '..', 'node_modules/openmct/dist/openmct.js'), saveAs: 'file-saver/src/FileSaver.js', bourbon: 'bourbon.scss', - printj: path.join(__dirname, '..', 'node_modules/printj/dist/printj.min.js'), + printj: join(__dirname, '..', 'node_modules/printj/dist/printj.min.js'), /** * OMM Paths **/ - types: path.join(__dirname, '..', 'src/types'), - services: path.join(__dirname, '..', 'src/services'), - lib: path.join(__dirname, '..', 'src/lib'), - tables: path.join(__dirname, '..', 'src/tables'), - ommUtils: path.join(__dirname, '..', 'src/utils'), + types: join(__dirname, '..', 'src/types'), + services: join(__dirname, '..', 'src/services'), + lib: join(__dirname, '..', 'src/lib'), + tables: join(__dirname, '..', 'src/tables'), + ommUtils: join(__dirname, '..', 'src/utils'), vue: 'vue/dist/vue.esm-bundler.js' } }, plugins: [ new webpack.DefinePlugin({ - __OMM_VERSION__: `'${packageDefinition.version}'`, + __OMM_VERSION__: `'${pkg.version}'`, __OMM_BUILD_DATE__: `'${new Date()}'`, __OMM_REVISION__: `'${gitRevision}'`, __OMM_BUILD_BRANCH__: `'${gitBranch}'`, @@ -182,4 +186,4 @@ const config = { stats: 'errors-warnings' }; -module.exports = config; +export default config; diff --git a/.webpack/webpack.dev.js b/.webpack/webpack.dev.js index 82aa5e3..056e1c6 100644 --- a/.webpack/webpack.dev.js +++ b/.webpack/webpack.dev.js @@ -2,19 +2,22 @@ This configuration should be used for development purposes. It contains full source map, a devServer (which be invoked using by `npm start`), and a non-minified Vue.js distribution. */ -const CopyWebpackPlugin = require('copy-webpack-plugin'); -const { merge } = require('webpack-merge'); -const common = require('./webpack.common'); -const path = require('path'); +import CopyWebpackPlugin from 'copy-webpack-plugin'; +import { merge } from 'webpack-merge'; +import common from './webpack.common.js'; +import { fileURLToPath } from 'url'; +import { parse, join, dirname } from 'path'; const proxyUrl = process.env.PROXY_URL || 'http://localhost:8080'; const apiUrl = process.env.API_URL ?? ''; const proxyHeaders = {}; +const __dirname = dirname(fileURLToPath(import.meta.url)); + if (process.env.COOKIE) { proxyHeaders.Cookie = process.env.COOKIE; } -module.exports = merge(common, { +export default merge(common, { mode: 'development', entry: { config: './config.js' @@ -48,7 +51,7 @@ module.exports = merge(common, { devServer: { devMiddleware: { writeToDisk: (filePathString) => { - const filePath = path.parse(filePathString); + const filePath = parse(filePathString); const shouldWrite = !filePath.base.includes('hot-update'); return shouldWrite; @@ -57,12 +60,12 @@ module.exports = merge(common, { watchFiles: ['src/**/*.css'], static: [ { - directory: path.join(__dirname, '..', 'node_modules/openmct/dist'), + directory: join(__dirname, '..', 'node_modules/openmct/dist'), publicPath: '/node_modules/openmct/dist', watch: false }, { - directory: path.join(__dirname, '..', 'test_data'), + directory: join(__dirname, '..', 'test_data'), publicPath: '/test_data', watch: false } diff --git a/.webpack/webpack.prod.js b/.webpack/webpack.prod.js index 9a2e87f..ff726ea 100644 --- a/.webpack/webpack.prod.js +++ b/.webpack/webpack.prod.js @@ -3,10 +3,10 @@ This configuration should be used for production installs. It is the default webpack configuration. */ -const { merge } = require('webpack-merge'); -const common = require('./webpack.common'); +import { merge } from 'webpack-merge'; +import common from './webpack.common.js'; /** @type {import('webpack').Configuration} */ -module.exports = merge(common, { +export default merge(common, { mode: 'production' }); diff --git a/README.md b/README.md index e270fdf..cca6f10 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Open Mission Control Technologies for Mission Control Web Services (Open MCT for MCWS) is a next-generation web-based mission control framework for visualization of data on desktop and mobile devices. Open MCT for MCWS is built on the [Open MCT Framework](https://github.com/nasa/openmct), and includes adapter code for using MCWS as a telemetry and persistence provider. Open MCT is developed at NASA Ames Research Center in Silicon Valley, in collaboration with NASA AMMOS and the Jet Propulsion Laboratory, California Institute of Technology (under its contract with NASA, 80NM0018D0004). ## Configuration -Various configurations and customizations are available by editing `config.js`. Descriptions of each configuration reside with the configuration in the file. +Various configurations and customizations are available by editing `default.yaml`. ### AMMOS configurations 1. `camUrl`: The url to the CAM server, if CAM is to be used for authentication. diff --git a/instances/default/assets/installCommonJsPlugin.js b/instances/default/assets/installCommonJsPlugin.js new file mode 100644 index 0000000..ed07d2f --- /dev/null +++ b/instances/default/assets/installCommonJsPlugin.js @@ -0,0 +1,24 @@ +import loadUmd from './load-umd.js'; +export default async function installCommonJsPlugin({openmct, importPath, installFunctionName, installFunctionOptions}) { + const imports = await loadUmd(importPath); + if (typeof imports === 'function') { + const installFunction = imports; + openmct.install(installFunction(installFunctionOptions)); + } else if (typeof imports === 'object') { + const exportedFunctionNames = Object.keys(imports); + if (exportedFunctionNames.length === 1) { + const resolvedInstallFunctionName = exportedFunctionNames[0]; + const installFunction = imports[resolvedInstallFunctionName]; + openmct.install(installFunction(installFunctionOptions)); + } else { + const exportedFunctionMap = exportedFunctionNames.reduce((map, key) => { + map.set(key.toLowerCase().replaceAll(/[^a-z0-9]/g, ''), imports[key]); + return map; + }, new Map()); + const installFunction = exportedFunctionMap.get(installFunctionName.toLowerCase().replaceAll(/[^a-z0-9]/g, '')); + openmct.install(installFunction(installFunctionOptions)); + } + } else { + console.error(`Unsupported import type for ${importPath}`); + } +} \ No newline at end of file diff --git a/instances/default/assets/installEs6Plugin.js b/instances/default/assets/installEs6Plugin.js new file mode 100644 index 0000000..ea26b1b --- /dev/null +++ b/instances/default/assets/installEs6Plugin.js @@ -0,0 +1,17 @@ +export default async function installEs6Plugin({openmct, importPath, installFunctionName, installFunctionOptions}) { + const imports = await import(importPath); + const exportedNames = Object.keys(imports); + // If only one export, assume it is the install function. This simplifies things for the 90% case of a single default export + if (exportedNames.length === 1) { + const resolvedInstallFunctionName = exportedNames[0]; + const installFunction = imports[resolvedInstallFunctionName]; + openmct.install(installFunction(installFunctionOptions)); + } else { + const exportedFunctionMap = Object.keys(imports).reduce((map, key) => { + map.set(key.toLowerCase().replaceAll(/[^a-z0-9]/g, ''), imports[key]); + return map; + }, new Map()); + const installFunction = exportedFunctionMap.get(installFunctionName.toLowerCase().replaceAll(/[^a-z0-9]/g, '')); + openmct.install(installFunction(installFunctionOptions)); + } +} \ No newline at end of file diff --git a/instances/default/assets/load-umd.js b/instances/default/assets/load-umd.js new file mode 100644 index 0000000..00cc87b --- /dev/null +++ b/instances/default/assets/load-umd.js @@ -0,0 +1,29 @@ +const savedModule = window.module; +const savedExports = window.savedExports; + +export default async function loadUmd(src) { + const mockExports = {}; + const mockModule = {exports: mockExports}; + + window.module = mockModule; + window.exports = mockExports; + + try { + await import(src); + const exports = window.module?.exports || window.exports; + + return exports; + } finally { + if (savedExports === undefined) { + delete window.exports; + } else { + window.exports = savedExports; + } + + if (savedModule === undefined) { + delete window.module; + } else { + window.module = savedModule; + } + } +} \ No newline at end of file diff --git a/instances/default/assets/mct-bootstrap-plugin/an-asset.txt b/instances/default/assets/mct-bootstrap-plugin/an-asset.txt new file mode 100644 index 0000000..c29d8ab --- /dev/null +++ b/instances/default/assets/mct-bootstrap-plugin/an-asset.txt @@ -0,0 +1 @@ +This is an asset! \ No newline at end of file diff --git a/instances/default/assets/mct-bootstrap-plugin/package.json b/instances/default/assets/mct-bootstrap-plugin/package.json new file mode 100644 index 0000000..a01f169 --- /dev/null +++ b/instances/default/assets/mct-bootstrap-plugin/package.json @@ -0,0 +1,12 @@ +{ + "name": "mct-bootstrap-plugin", + "version": "0.0.1", + "description": "Bootstraps and initializes Open MCT", + "main": "plugin.js", + "type": "module", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "National Aeronautics and Space Administration", + "license": "UNLICENSED" +} diff --git a/instances/default/assets/mct-bootstrap-plugin/plugin.js b/instances/default/assets/mct-bootstrap-plugin/plugin.js new file mode 100644 index 0000000..201752d --- /dev/null +++ b/instances/default/assets/mct-bootstrap-plugin/plugin.js @@ -0,0 +1,48 @@ +const simpleTimeMathRegex = /^(now)?\s*([-+]?\s*\d+)?$/; + +function getEpochTime(timeExpression) { + const now = Date.now(); + const regexResult = simpleTimeMathRegex.exec(timeExpression); + if (regexResult) { + const isOffsetFromNow = regexResult[1] === 'now'; + if (isOffsetFromNow) { + const offsetString = regexResult[2] || '0'; + const offset = parseInt(offsetString.replaceAll(' ', ''), 10); + return now + offset; + + } + return timeExpression; + } + return timeExpression; +} +export function helloPanda() { + return function install(openmct) { + openmct.once('start', () => { + alert('hello panda'); + }); + } +} +export function testRelativeAssetPaths({testAsset}) { + return function install(openmct) { + openmct.once('start', async () => { + const asset = await fetch(testAsset); + const assetText = await asset.text(); + console.log(`Asset text: ${assetText}`); + }); + } +} +export function mctBootstrapPlugin({timeSystem, clock, start, end, startOffset, endOffset, mode} = {timeSystem: 'utc', clock: 'local', start: 'now - 900000', end: 'now', startOffset: -60000, endOffset: 0, mode: 'realtime'}) { + return function install(openmct) { + openmct.install(openmct.plugins.UTCTimeSystem()); + openmct.time.setTimeSystem(timeSystem); + openmct.time.setClock(clock); + + if (mode === 'fixed') { + start = getEpochTime(start); + end = getEpochTime(end); + openmct.time.setMode(mode, {start, end}); + } else { + openmct.time.setMode(mode, {start: startOffset, end: endOffset}); + } + } +} \ No newline at end of file diff --git a/instances/default/assets/openmct-configuration-schema.json b/instances/default/assets/openmct-configuration-schema.json new file mode 100644 index 0000000..b3d87c9 --- /dev/null +++ b/instances/default/assets/openmct-configuration-schema.json @@ -0,0 +1,111 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "OpenMct Configuration Schema", + "description": "Schema for OpenMCT configuration YAML", + "definitions": { + "pluginMap": { + "type": "object", + "patternProperties": { + "^.*$": { + "$ref": "#/definitions/plugin" + } + } + }, + "plugin": { + "type": "object", + "properties": { + "source": { + "type": "string", + "enum": ["npm", "builtin"], + "default": "builtin" + }, + "npmPackage": { + "type": "string", + "description": "NPM package that provides this plugin." + }, + "installFunction": { + "type": "string", + "description": "Name of the function that is exported from the npm package and which provides this plugin." + }, + "entryPoint": { + "type": "string", + "description": "Script that exports the install function(s)" + }, + "enabled": { + "type": "boolean", + "description": "Whether the plugin should be enabled. By setting this to false you can override default plugins" + }, + "options": { + "oneOf": [ + { + "type": "object", + "description": "Options to configure the plugin" + }, + { + "type": "array", + "description": "Arguments to be provided to a plugin install function" + } + ], + "description": "The options to be passed to the plugin at install time. Can be either an object or an array. If an array is provided, each member will be treated as an argument to the plugin install function." + } + }, + "additionalProperties": true + } + }, + "type": "object", + "required": ["openmct"], + "properties": { + "openmct": { + "type": "object", + "oneOf": [ + { + "properties": { + "version": { + "type": "string", + "description": "Version of OpenMCT to use", + "default": "latest" + }, + "plugins": { + "type": "array", + "items": { + "oneOf": [ + { + "type": "string", + "description": "Plugin identifier (e.g., 'openmct.Plugins.LocalStorage')" + },{ + "$ref": "#/definitions/pluginMap" + } + ] + } + } + }, + "additionalProperties": false, + "required": ["version"] + }, + { + "properties": { + "npmPackage": { + "type": "string", + "description": "NPM package that provides Open MCT. If present this will override any Open MCT version specified." + }, + "plugins": { + "type": "array", + "items": { + "oneOf": [ + { + "type": "string", + "description": "Plugin identifier (e.g., 'openmct.Plugins.LocalStorage')" + },{ + "$ref": "#/definitions/pluginMap" + } + ] + } + } + }, + "additionalProperties": false, + "required": ["npmPackage"] + } + ] + } + } + } \ No newline at end of file diff --git a/instances/default/index.html b/instances/default/index.html new file mode 100644 index 0000000..1b36bee --- /dev/null +++ b/instances/default/index.html @@ -0,0 +1,30 @@ + + Open MCT + + + + + + + + + \ No newline at end of file diff --git a/instances/default/instance.yaml b/instances/default/instance.yaml new file mode 100644 index 0000000..3e4bea6 --- /dev/null +++ b/instances/default/instance.yaml @@ -0,0 +1,74 @@ +# yaml-language-server: $schema=assets/openmct-configuration-schema.json +openmct: + version: latest + plugins: + - mct-bootstrap-plugin: + npmPackage: file:./assets/mct-bootstrap-plugin + options: + timeSystem: utc + clock: local + startOffset: -60000 + endOffset: 0 + mode: realtime + - openmct.plugins.Espresso + - openmct.plugins.MyItems + - openmct.plugins.LocalStorage + - openmct.plugins.UTCTimeSystem + - openmct.plugins.PlanLayout: + options: + creatable: true + - openmct.plugins.DisplayLayout: + options: + showAsView: + - summary-widget + - vista.packetSummaryEvents + - vista.dataProducts + - vista.packets + - vista.frameSummary + - vista.frameWatch + - openmct.plugins.Snow + - openmct.plugins.ObjectMigration + - openmct.plugins.ClearData: + options: + - - table + - telemetry.plot.overlay + - telemetry.plot.stacked + - vista.packetSummaryEvents + - vista.dataProducts + - vista.packets + - vista.frameSummary + - vista.frameWatch + - vista.chanTableGroup + - indicator: false + - openmct.plugins.Filters: + options: + - vista.alarmsView + - telemetry.plot.overlay + - table + - vista.chanTableGroup + - vista.commandEventsView + - vista.messagesView + - vista.evrView + - openmct.plugins.Notebook + - openmct.plugins.Clock: + options: + useClockIndicator: false + - openmct.plugins.DefaultRootName: + options: + - VISTA + - openmct-mcws-plugin: + npmPackage: file:/Users/jjviglio/OpenMCT/OMM/openmct-mcws/ + entryPoint: dist/openmct-mcws-plugin.js + options: + useDeveloperStorage: true + proxyUrl: http://localhost:8080/ + camUrl: '' + mcwsUrl: '' + namespaces: + - key: r50-dev + name: R5.0 Shared + url: '' + - userNamespace: true + key: r50-dev + name: R5.0 Users + url: '' diff --git a/instances/default/package.json b/instances/default/package.json new file mode 100644 index 0000000..002aa25 --- /dev/null +++ b/instances/default/package.json @@ -0,0 +1,17 @@ +{ + "name": "default", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "mct-bootstrap-plugin": "file:assets/mct-bootstrap-plugin", + "openmct": "^4.1.0", + "openmct-mcws": "file:../.." + } +} diff --git a/instances/development/assets/installBuiltinPlugin.js b/instances/development/assets/installBuiltinPlugin.js new file mode 100644 index 0000000..872d818 --- /dev/null +++ b/instances/development/assets/installBuiltinPlugin.js @@ -0,0 +1,9 @@ +import { substituteVariables, runtimeSubstitutions } from "./mct-builder-core.js"; + +export default async function installBuiltinPlugin({openmct, installFunction, installFunctionOptions, buildTimeSubstitutions}) { + const optionsWithSubstitutions = substituteVariables(installFunctionOptions, { + ...buildTimeSubstitutions, + ...runtimeSubstitutions + }); + openmct.install(installFunction(optionsWithSubstitutions)); +} \ No newline at end of file diff --git a/instances/development/assets/installCommonJsPlugin.js b/instances/development/assets/installCommonJsPlugin.js new file mode 100644 index 0000000..5b0092c --- /dev/null +++ b/instances/development/assets/installCommonJsPlugin.js @@ -0,0 +1,36 @@ +import {loadUmd, substituteVariables, getEpochTime} from './mct-builder-core.js'; + +const runtimeSubstitutions = { + '/(.*)(\\${now})(.*)/': (originalProperty) => { + return getEpochTime(originalProperty); + } +} + +export default async function installCommonJsPlugin({openmct, importPath, installFunctionName, installFunctionOptions, buildTimeSubstitutions}) { + const optionsWithSubstitutions = substituteVariables(installFunctionOptions, { + ...buildTimeSubstitutions, + ...runtimeSubstitutions, + }); + + const imports = await loadUmd(importPath); + if (typeof imports === 'function') { + const installFunction = imports; + openmct.install(installFunction(optionsWithSubstitutions)); + } else if (typeof imports === 'object') { + const exportedFunctionNames = Object.keys(imports); + if (exportedFunctionNames.length === 1) { + const resolvedInstallFunctionName = exportedFunctionNames[0]; + const installFunction = imports[resolvedInstallFunctionName]; + openmct.install(installFunction(optionsWithSubstitutions)); + } else { + const exportedFunctionMap = exportedFunctionNames.reduce((map, key) => { + map.set(key.toLowerCase().replaceAll(/[^a-z0-9]/g, ''), imports[key]); + return map; + }, new Map()); + const installFunction = exportedFunctionMap.get(installFunctionName.toLowerCase().replaceAll(/[^a-z0-9]/g, '')); + openmct.install(installFunction(optionsWithSubstitutions)); + } + } else { + console.error(`Unsupported import type for ${importPath}`); + } +} \ No newline at end of file diff --git a/instances/development/assets/installEs6Plugin.js b/instances/development/assets/installEs6Plugin.js new file mode 100644 index 0000000..6a25466 --- /dev/null +++ b/instances/development/assets/installEs6Plugin.js @@ -0,0 +1,24 @@ +import { substituteVariables, runtimeSubstitutions } from "./mct-builder-core.js"; + +export default async function installEs6Plugin({openmct, importPath, installFunctionName, installFunctionOptions, buildTimeSubstitutions}) { + const imports = await import(importPath); + const exportedNames = Object.keys(imports); + + const optionsWithSubstitutions = substituteVariables(installFunctionOptions, { + ...buildTimeSubstitutions, + ...runtimeSubstitutions + }); + // If only one export, assume it is the install function. This simplifies things for the 90% case of a single default export + if (exportedNames.length === 1) { + const resolvedInstallFunctionName = exportedNames[0]; + const installFunction = imports[resolvedInstallFunctionName]; + openmct.install(installFunction(optionsWithSubstitutions)); + } else { + const exportedFunctionMap = Object.keys(imports).reduce((map, key) => { + map.set(key.toLowerCase().replaceAll(/[^a-z0-9]/g, ''), imports[key]); + return map; + }, new Map()); + const installFunction = exportedFunctionMap.get(installFunctionName.toLowerCase().replaceAll(/[^a-z0-9]/g, '')); + openmct.install(installFunction(optionsWithSubstitutions)); + } +} \ No newline at end of file diff --git a/instances/development/assets/load-umd.js b/instances/development/assets/load-umd.js new file mode 100644 index 0000000..00cc87b --- /dev/null +++ b/instances/development/assets/load-umd.js @@ -0,0 +1,29 @@ +const savedModule = window.module; +const savedExports = window.savedExports; + +export default async function loadUmd(src) { + const mockExports = {}; + const mockModule = {exports: mockExports}; + + window.module = mockModule; + window.exports = mockExports; + + try { + await import(src); + const exports = window.module?.exports || window.exports; + + return exports; + } finally { + if (savedExports === undefined) { + delete window.exports; + } else { + window.exports = savedExports; + } + + if (savedModule === undefined) { + delete window.module; + } else { + window.module = savedModule; + } + } +} \ No newline at end of file diff --git a/instances/development/assets/mct-bootstrap-plugin/an-asset.txt b/instances/development/assets/mct-bootstrap-plugin/an-asset.txt new file mode 100644 index 0000000..c29d8ab --- /dev/null +++ b/instances/development/assets/mct-bootstrap-plugin/an-asset.txt @@ -0,0 +1 @@ +This is an asset! \ No newline at end of file diff --git a/instances/development/assets/mct-bootstrap-plugin/package.json b/instances/development/assets/mct-bootstrap-plugin/package.json new file mode 100644 index 0000000..a01f169 --- /dev/null +++ b/instances/development/assets/mct-bootstrap-plugin/package.json @@ -0,0 +1,12 @@ +{ + "name": "mct-bootstrap-plugin", + "version": "0.0.1", + "description": "Bootstraps and initializes Open MCT", + "main": "plugin.js", + "type": "module", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "National Aeronautics and Space Administration", + "license": "UNLICENSED" +} diff --git a/instances/development/assets/mct-bootstrap-plugin/plugin.js b/instances/development/assets/mct-bootstrap-plugin/plugin.js new file mode 100644 index 0000000..201752d --- /dev/null +++ b/instances/development/assets/mct-bootstrap-plugin/plugin.js @@ -0,0 +1,48 @@ +const simpleTimeMathRegex = /^(now)?\s*([-+]?\s*\d+)?$/; + +function getEpochTime(timeExpression) { + const now = Date.now(); + const regexResult = simpleTimeMathRegex.exec(timeExpression); + if (regexResult) { + const isOffsetFromNow = regexResult[1] === 'now'; + if (isOffsetFromNow) { + const offsetString = regexResult[2] || '0'; + const offset = parseInt(offsetString.replaceAll(' ', ''), 10); + return now + offset; + + } + return timeExpression; + } + return timeExpression; +} +export function helloPanda() { + return function install(openmct) { + openmct.once('start', () => { + alert('hello panda'); + }); + } +} +export function testRelativeAssetPaths({testAsset}) { + return function install(openmct) { + openmct.once('start', async () => { + const asset = await fetch(testAsset); + const assetText = await asset.text(); + console.log(`Asset text: ${assetText}`); + }); + } +} +export function mctBootstrapPlugin({timeSystem, clock, start, end, startOffset, endOffset, mode} = {timeSystem: 'utc', clock: 'local', start: 'now - 900000', end: 'now', startOffset: -60000, endOffset: 0, mode: 'realtime'}) { + return function install(openmct) { + openmct.install(openmct.plugins.UTCTimeSystem()); + openmct.time.setTimeSystem(timeSystem); + openmct.time.setClock(clock); + + if (mode === 'fixed') { + start = getEpochTime(start); + end = getEpochTime(end); + openmct.time.setMode(mode, {start, end}); + } else { + openmct.time.setMode(mode, {start: startOffset, end: endOffset}); + } + } +} \ No newline at end of file diff --git a/instances/development/assets/mct-builder-core.d.ts b/instances/development/assets/mct-builder-core.d.ts new file mode 100644 index 0000000..b31b9e1 --- /dev/null +++ b/instances/development/assets/mct-builder-core.d.ts @@ -0,0 +1,27 @@ + +type SubstitutionFunction = (originalProperty: string, regexResult: RegExpExecArray) => string; + +declare module '../assets/mct-builder-core.js' { + /** + * Substitutes variables in the input object based on the provided variables + * @param pluginOptions The input object to process + * @param variables Key-value pairs of variables to substitute. If a function is defined, the function will be called to generate the substituted value + * + * @returns The input object with variables substituted + */ + export function substituteVariables(pluginOptions: object, variables: Record): T; + + /** + * Parses a time expression and returns the corresponding epoch time + * @param timeExpression Time expression to parse (e.g., '${now}' or '${now}+1000') + * + * @returns The time in the JavaScript epoch (in ms) corresponding to the time expression + */ + export function getEpochTime(timeExpression: string): number; + + /** + * Loads a UMD module + * @param src Source URL or path of the module to load + */ + export function loadUmd(src: string): Promise; +} \ No newline at end of file diff --git a/instances/development/assets/mct-builder-core.js b/instances/development/assets/mct-builder-core.js new file mode 100644 index 0000000..afbb35c --- /dev/null +++ b/instances/development/assets/mct-builder-core.js @@ -0,0 +1,114 @@ +const savedModule = window.module; +const savedExports = window.savedExports; + +export function substituteVariables(input, variables) { + if (input === undefined || variables === undefined) { + return undefined; + } + return JSON.parse(JSON.stringify(input, (instanceConfigurationKey, instanceConfigurationValue) => { + let result = instanceConfigurationValue; + + if (typeof instanceConfigurationValue === 'string') { + for (const [replacementVariableKey, replacementVariableValue] of Object.entries(variables)) { + if (replacementVariableKey.startsWith('/')) { + const regex = new RegExp(replacementVariableKey.slice(1, replacementVariableKey.length - 1)); + if (regex.test(result)) { + if (typeof replacementVariableValue === 'function') { + result = replacementVariableValue(result, regex.exec(result)); + } + } + } else if (result.includes(replacementVariableKey)){ + result = result.replaceAll(replacementVariableKey, replacementVariableValue); + } + } + } + + return result; + })); +} + +export async function loadUmd(src) { + const mockExports = {}; + const mockModule = {exports: mockExports}; + + window.module = mockModule; + window.exports = mockExports; + + try { + await import(src); + const exports = window.module?.exports || window.exports; + + return exports; + } finally { + if (savedExports === undefined) { + delete window.exports; + } else { + window.exports = savedExports; + } + + if (savedModule === undefined) { + delete window.module; + } else { + window.module = savedModule; + } + } +} +const timeMathSubstitutionsInternal = { + 'now': () => { + return Date.now(); + }, + 'five_seconds': 5000, + 'ten_seconds': 10000, + 'fifteen_seconds': 15000, + 'thirty_seconds': 30000, + 'one_minute': 60000, + 'five_minutes': 300000, + 'ten_minutes': 600000, + 'fifteen_minutes': 900000, + 'thirty_minutes': 1800000, + 'one_hour': 3600000, + 'two_hours': 7200000, + 'one_day': 86400000, + 'one_week': 604800000, + 'one_month': 2592000000, + 'one_year': 31536000000, + 'two_years': 63072000000, + 'five_years': 157680000000, + 'ten_years': 315360000000 +} +const joined = Object.keys(timeMathSubstitutionsInternal).join('|'); +const regex = `/\\\${(${joined})}/`; +const compiledRegex = new RegExp(regex.slice(1, regex.length - 1), 'g'); +const runtimeSubstitutionsInternal = {}; +runtimeSubstitutionsInternal[regex] = (originalProperty) => { + return getEpochTime(originalProperty); +}; +const simpleTimeMathRegex = /^(\d+)?\s*([-+]?\s*\d+)?$/; + +export const runtimeSubstitutions = runtimeSubstitutionsInternal; +export const timeMathSubstitutions = timeMathSubstitutionsInternal; + +export function getEpochTime(timeExpression) { + timeExpression = timeExpression.replaceAll(compiledRegex, (match, p1) => { + const timeSubstitution = timeMathSubstitutions[p1]; + if (typeof timeSubstitution === 'function') { + return timeSubstitution(); + } else { + return timeSubstitution; + } + }); + return parseSimpleTimeMath(timeExpression); +} + +function parseSimpleTimeMath(timeExpression) { + const match = simpleTimeMathRegex.exec(timeExpression); + if (match) { + return match.slice(1).reduce((acc, val) => { + if (val === undefined) { + return acc; + } else { + return acc + parseInt(val.replaceAll(' ', '')) || 0; + } + }, 0); + } +} \ No newline at end of file diff --git a/instances/development/assets/openmct-configuration-schema.json b/instances/development/assets/openmct-configuration-schema.json new file mode 100644 index 0000000..b3d87c9 --- /dev/null +++ b/instances/development/assets/openmct-configuration-schema.json @@ -0,0 +1,111 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "OpenMct Configuration Schema", + "description": "Schema for OpenMCT configuration YAML", + "definitions": { + "pluginMap": { + "type": "object", + "patternProperties": { + "^.*$": { + "$ref": "#/definitions/plugin" + } + } + }, + "plugin": { + "type": "object", + "properties": { + "source": { + "type": "string", + "enum": ["npm", "builtin"], + "default": "builtin" + }, + "npmPackage": { + "type": "string", + "description": "NPM package that provides this plugin." + }, + "installFunction": { + "type": "string", + "description": "Name of the function that is exported from the npm package and which provides this plugin." + }, + "entryPoint": { + "type": "string", + "description": "Script that exports the install function(s)" + }, + "enabled": { + "type": "boolean", + "description": "Whether the plugin should be enabled. By setting this to false you can override default plugins" + }, + "options": { + "oneOf": [ + { + "type": "object", + "description": "Options to configure the plugin" + }, + { + "type": "array", + "description": "Arguments to be provided to a plugin install function" + } + ], + "description": "The options to be passed to the plugin at install time. Can be either an object or an array. If an array is provided, each member will be treated as an argument to the plugin install function." + } + }, + "additionalProperties": true + } + }, + "type": "object", + "required": ["openmct"], + "properties": { + "openmct": { + "type": "object", + "oneOf": [ + { + "properties": { + "version": { + "type": "string", + "description": "Version of OpenMCT to use", + "default": "latest" + }, + "plugins": { + "type": "array", + "items": { + "oneOf": [ + { + "type": "string", + "description": "Plugin identifier (e.g., 'openmct.Plugins.LocalStorage')" + },{ + "$ref": "#/definitions/pluginMap" + } + ] + } + } + }, + "additionalProperties": false, + "required": ["version"] + }, + { + "properties": { + "npmPackage": { + "type": "string", + "description": "NPM package that provides Open MCT. If present this will override any Open MCT version specified." + }, + "plugins": { + "type": "array", + "items": { + "oneOf": [ + { + "type": "string", + "description": "Plugin identifier (e.g., 'openmct.Plugins.LocalStorage')" + },{ + "$ref": "#/definitions/pluginMap" + } + ] + } + } + }, + "additionalProperties": false, + "required": ["npmPackage"] + } + ] + } + } + } \ No newline at end of file diff --git a/instances/development/index.html b/instances/development/index.html new file mode 100644 index 0000000..be60121 --- /dev/null +++ b/instances/development/index.html @@ -0,0 +1,34 @@ + + Open MCT + + + + + + + + + \ No newline at end of file diff --git a/instances/development/instance.yaml b/instances/development/instance.yaml new file mode 100644 index 0000000..cc16697 --- /dev/null +++ b/instances/development/instance.yaml @@ -0,0 +1,124 @@ +# yaml-language-server: $schema=assets/openmct-configuration-schema.json +openmct: + version: latest + plugins: + - mct-bootstrap-plugin: + npmPackage: file:./assets/mct-bootstrap-plugin + options: + timeSystem: utc + clock: local + startOffset: -60000 + endOffset: 0 + mode: realtime + - openmct.plugins.Espresso + - openmct.plugins.MyItems + - openmct.plugins.LocalStorage + - openmct.plugins.UTCTimeSystem + - openmct.plugins.PlanLayout: + options: + creatable: true + - openmct.plugins.DisplayLayout: + options: + showAsView: + - summary-widget + - vista.packetSummaryEvents + - vista.dataProducts + - vista.packets + - vista.frameSummary + - vista.frameWatch + - openmct.plugins.Conductor: + options: + menuOptions: + - name: Fixed + timeSystem: utc + bounds: + start: ${now} - ${thirty_minutes} + end: ${now} + presets: + - label: Last Day + bounds: + start: ${now} - ${one_day} + end: ${now} + - label: Last 2 hours + bounds: + start: ${now} - ${two_hours} + end: ${now} + - label: Last hour + bounds: + start: ${now} - ${one_hour} + end: ${now} + records: 10 + - name: Realtime + timeSystem: utc + clock: local + clockOffsets: + start: '-${thirty_minutes}' + end: ${thirty_seconds} + presets: + - label: 1 Hour + bounds: + start: '-${one_hour}' + end: ${thirty_seconds} + - label: 30 Minutes + bounds: + start: '-${thirty_minutes}' + end: ${thirty_seconds} + - label: 15 Minutes + bounds: + start: '-${fifteen_minutes}' + end: ${thirty_seconds} + - label: 5 Minutes + bounds: + start: '-${five_minutes}' + end: ${thirty_seconds} + - label: 1 Minute + bounds: + start: '-${one_minute}' + end: ${thirty_seconds} + - openmct.plugins.Snow + - openmct.plugins.ObjectMigration + - openmct.plugins.ClearData: + options: + - - table + - telemetry.plot.overlay + - telemetry.plot.stacked + - vista.packetSummaryEvents + - vista.dataProducts + - vista.packets + - vista.frameSummary + - vista.frameWatch + - vista.chanTableGroup + - indicator: false + - openmct.plugins.Filters: + options: + - - vista.alarmsView + - telemetry.plot.overlay + - table + - vista.chanTableGroup + - vista.commandEventsView + - vista.messagesView + - vista.evrView + - openmct.plugins.Notebook + - openmct.plugins.Clock: + options: + useClockIndicator: false + - openmct.plugins.DefaultRootName: + options: + - VISTA + - openmct-mcws-plugin: + npmPackage: file:/Users/jjviglio/OpenMCT/OMM/openmct-mcws/ + entryPoint: dist/openmct-mcws-plugin.js + options: + useDeveloperStorage: true + proxyUrl: http://localhost:8080/ + camUrl: '' + mcwsUrl: '' + namespaces: + - key: r50-dev + name: R5.0 Shared + url: '' + - userNamespace: true + key: r50-dev + name: R5.0 Users + url: '' + - openmct.plugins.example.Generator diff --git a/instances/development/package.json b/instances/development/package.json new file mode 100644 index 0000000..eb7f9b5 --- /dev/null +++ b/instances/development/package.json @@ -0,0 +1,18 @@ +{ + "name": "development", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "mct-bootstrap-plugin": "file:assets/mct-bootstrap-plugin", + "openmct": "^4.1.0", + "openmct-mcws": "file:../..", + "openmct-mcws-plugin": "file:../.." + } +} diff --git a/loader.js b/loader.js deleted file mode 100644 index 687cc23..0000000 --- a/loader.js +++ /dev/null @@ -1,165 +0,0 @@ -define([ - 'openmct', - './src/AMMOSPlugins', - './src/styles/sass/vista.scss', - './src/commandEventsView/plugin', - './src/messagesView/plugin', - './src/product-status/plugin', - './about.html', - './src/metadataAction/plugin', - './src/clearDataIndicator/plugin', - './src/dictionaryView/plugin', - './src/packetSummary/plugin', - './src/containerView/plugin', - 'services/identity/MCWSIdentityProvider', - './src/persistence/plugin' -], function ( - openmct, - AMMOSPlugins, - VistaStyles /** Do not delete, needed for webpack to compile scss file*/, - CommandEventsViewPlugin, - MessagesPlugin, - ProductStatusPlugin, - AboutTemplate, - MetadataActionPlugin, - ClearDataIndicator, - DictionaryViewPlugin, - PacketSummaryPlugin, - ContainerViewPlugin, - IdentityProvider, - MCWSPersistenceProviderPlugin -) { - function loader(config) { - let persistenceLoaded; - const persistenceLoadedPromise = new Promise((resolve) => { - persistenceLoaded = resolve; - }); - openmct.setAssetPath(config.assetPath); - - //Optional Themes - if (config.theme) { - openmct.install(openmct.plugins[config.theme]()); - } else { - openmct.install(openmct.plugins.Snow()); - } - - openmct.install( - openmct.plugins.Filters([ - 'vista.alarmsView', - 'telemetry.plot.overlay', - 'table', - 'vista.chanTableGroup', - 'vista.commandEventsView', - 'vista.messagesView', - 'vista.evrView' - ]) - ); - openmct.install(openmct.plugins.ObjectMigration()); - openmct.install( - openmct.plugins.DisplayLayout({ - showAsView: [ - 'summary-widget', - 'vista.packetSummaryEvents', - 'vista.dataProducts', - 'vista.packets', - 'vista.frameSummary', - 'vista.frameWatch' - ] - }) - ); - openmct.install( - openmct.plugins.ClearData( - [ - 'table', - 'telemetry.plot.overlay', - 'telemetry.plot.stacked', - 'vista.packetSummaryEvents', - 'vista.dataProducts', - 'vista.packets', - 'vista.frameSummary', - 'vista.frameWatch', - 'vista.chanTableGroup' - ], - { - indicator: false - } - ) - ); - openmct.install(ClearDataIndicator.default(config.globalStalenessInterval)); - openmct.install(CommandEventsViewPlugin.default(config.tablePerformanceOptions)); - openmct.install(MessagesPlugin.default(config.tablePerformanceOptions)); - openmct.install(ProductStatusPlugin.default(config.tablePerformanceOptions)); - openmct.install(openmct.plugins.UTCTimeSystem()); - openmct.install(openmct.plugins.Notebook()); - openmct.install(MetadataActionPlugin.default()); - openmct.install(DictionaryViewPlugin.default(config.tablePerformanceOptions)); - openmct.install(PacketSummaryPlugin.default(config.tablePerformanceOptions)); - openmct.install(ContainerViewPlugin.default()); - openmct.install(openmct.plugins.Clock({ useClockIndicator: false })); - - openmct.install(new AMMOSPlugins(config)); - - openmct.user.setProvider(new IdentityProvider.default(openmct)); - - if (config.useDeveloperStorage) { - openmct.install(openmct.plugins.LocalStorage()); - openmct.install(openmct.plugins.MyItems()); - persistenceLoaded(); - } else { - const mcwsPersistenceProvider = MCWSPersistenceProviderPlugin.default(config.namespaces); - openmct.install(async (_openmct) => { - await mcwsPersistenceProvider(_openmct); - persistenceLoaded(); - }); - } - - // install optional plugins - if (config.plugins) { - Object.entries(config.plugins).forEach(([plugin, pluginConfig]) => { - const examplePluginExists = openmct.plugins.example[plugin]; - const pluginExists = openmct.plugins[plugin] || examplePluginExists; - const pluginEnabled = pluginConfig?.enabled; - const installPlugin = pluginExists && pluginEnabled; - - if (installPlugin) { - if (examplePluginExists) { - openmct.install(openmct.plugins.example[plugin](...(pluginConfig.configuration ?? []))); - } else { - openmct.install(openmct.plugins[plugin](...(pluginConfig.configuration ?? []))); - } - } else if (!pluginExists) { - console.warn(`Plugin ${plugin} does not exist. Check the plugin name and try again.`); - } - }); - } - - openmct.branding({ - aboutHtml: insertBuildInfo(AboutTemplate) - }); - - // do not show telemetry if it falls out of bounds - // even if there is no new telemetry - openmct.telemetry.greedyLAD(false); - - persistenceLoadedPromise.then(() => { - openmct.start(); - window.openmct = openmct; - }); - } - - /** - * Replaces placeholders in the HTML with build info provided by webpack. - * Build info is defined in webpack config, and is exposed as global - * JavaScript variables - * @param {*} markup - */ - function insertBuildInfo(markup) { - return markup - .replace(/\$\{project\.version\}/g, __OMM_VERSION__) - .replace(/\$\{timestamp\}/g, __OMM_BUILD_DATE__) - .replace(/\$\{buildNumber\}/g, __OMM_REVISION__) - .replace(/\$\{branch\}/g, __OMM_BUILD_BRANCH__); - } - - return loader; -}); diff --git a/package.json b/package.json index 2a28496..ed79ae6 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { - "name": "openmct-mcws", + "name": "openmct-mcws-plugin", "version": "v5.4.0-rc3", "description": "Open MCT for MCWS", - "main": "dist/openmct-mcws.js", + "main": "dist/openmct-mcws-plugin.js", "type": "module", "devDependencies": { "@babel/eslint-parser": "7.26.8", @@ -53,10 +53,12 @@ "vue": "3.4.24", "vue-eslint-parser": "^9.4.3", "vue-loader": "16.8.3", - "webpack": "5.94.0", + "webpack": "^5.94.0", "webpack-cli": "5.1.1", "webpack-dev-server": "5.0.2", - "webpack-merge": "5.10.0" + "webpack-merge": "5.10.0", + "express": "^4.18.2", + "http-proxy-middleware": "^2.0.6" }, "peerDependencies": { "openmct": "^4.1.0" @@ -72,7 +74,8 @@ "build:watch": "webpack --config ./.webpack/webpack.dev.js --watch", "test": "karma start --single-run", "jshint": "jshint src/**/*.js || exit 0", - "prepare": "npm run build:prod" + "serve": "node serve-instance.js", + "prepare": "node -e \"if (require('fs').existsSync('node_modules/webpack-cli')) { require('child_process').execSync('npm run build:prod', {stdio: 'inherit'}) }\" || true" }, "repository": { "type": "git", @@ -86,4 +89,4 @@ }, "author": "", "license": "Apache-2.0" -} \ No newline at end of file +} diff --git a/plugin.js b/plugin.js new file mode 100644 index 0000000..fc6c538 --- /dev/null +++ b/plugin.js @@ -0,0 +1,277 @@ +import AboutTemplate from './about.html'; +import VistaStyles from './src/styles/sass/vista.scss'; /** Do not delete, needed for webpack to compile scss file*/ +import CommandEventsViewPlugin from './src/commandEventsView/plugin.js'; +import TaxonomyPlugin from './src/taxonomy/plugin.js'; +import HistoricalTelemetryPlugin from './src/historical/plugin.js'; +import LinkPlugin from './src/link/plugin.js'; +import FormatPlugin from './src/formats/plugin.js'; +import MessagesPlugin from './src/messagesView/plugin.js'; +import ProductStatusPlugin from './src/product-status/plugin.js'; +import MetadataActionPlugin from './src/metadataAction/plugin.js'; +import ClearDataIndicatorPlugin from './src/clearDataIndicator/plugin.js'; +import DictionaryViewPlugin from './src/dictionaryView/plugin.js'; +import PacketSummaryPlugin from './src/packetSummary/plugin.js'; +import ContainerViewPlugin from './src/containerView/plugin.js'; +import IdentityProvider from './src/services/identity/MCWSIdentityProvider.js'; +import MCWSPersistenceProviderPlugin from './src/persistence/plugin.js'; +import DatasetCache from './src/services/dataset/DatasetCache.js'; +import SessionService from './src/services/session/SessionService.js'; +import GlobalStaleness from './src/services/globalStaleness/globalStaleness.js'; +import TypePlugin from './src/types/plugin.js'; +import TimePlugin from './src/time/plugin.js'; +import getVistaTime from './src/services/time/vistaTime.js'; +import RealtimeTelemetryPlugin from './src/realtime/plugin.js'; +import VenuePlugin from './src/venues/plugin.js'; +import mcwsClient from './src/services/mcws/MCWSClient.js'; +import UTCDayOfYearFormat from './src/formats/UTCDayOfYearFormat.js'; +import FrameWatchViewPlugin from './src/frameSummary/plugin.js'; +import FrameEventFilterViewPlugin from './src/frameEventFilterView/plugin.js'; +import ChannelTablePlugin from './src/channelTable/channelTablePlugin/plugin.js'; +import ChannelTableSetPlugin from './src/channelTable/channelTableSetPlugin/plugin.js'; +import ChannelLimitsPlugin from './src/channelLimits/plugin.js'; +import FrameAccountabilityPlugin from './src/frameAccountability/plugin.js'; +import AlarmsViewPlugin from './src/alarmsView/plugin.js'; +import EVRViewPlugin from './src/evrView/plugin.js'; +import CustomFormatterPlugin from './src/customFormatter/plugin.js'; +import CustomFormsPlugin from './src/customForms/plugin.js'; +import ActionModifiersPlugin from './src/actionModifiers/plugin.js'; +import RealtimeIndicatorPlugin from './src/realtimeIndicator/plugin.js'; +import PacketQueryPlugin from './src/packetQuery/plugin.js'; +import MCWSIndicatorPlugin from './src/mcwsIndicator/plugin.js'; +import MultipleHistoricalSessions from './src/multipleHistoricalSessions/plugin.js'; +import RealtimeSessions from './src/realtimeSessions/plugin.js'; +import GlobalFilters from './src/globalFilters/plugin.js'; +import ExportDataAction from './src/exportDataAction/plugin.js'; + +export default function openmctMCWSPlugin(options) { + return function install(openmct) { + const defaultConfig = { + venueAware: { + enabled: false, + venues: 'ExampleVenueDefinitions.json' + }, + taxonomy: { + evrDefaultBackgroundColor: undefined, + evrDefaultForegroundColor: undefined, + evrBackgroundColorByLevel: { + FATAL: '#ff0000', + WARNING_HI: '#ff7f24', + WARNING_LO: '#ffff00', + COMMAND: '#00bfff', + ACTIVITY_HI: '#6d6d6d', + ACTIVITY_LO: '#dcdcdc', + DIAGNOSTIC: '#00ff00', + EVR_UNKNOWN: '#00ff00', + FAULT: '#ff0000', + WARNING: '#ff7f24' + }, + evrForegroundColorByLevel: { + FATAL: '#ffffff', + WARNING_HI: '#000000', + WARNING_LO: '#000000', + COMMAND: '#ffffff', + ACTIVITY_HI: '#ffffff', + ACTIVITY_LO: '#000000', + DIAGNOSTIC: '#000000', + EVR_UNKNOWN: '#000000', + FAULT: '#ffffff', + WARNING: '#000000' + } + }, + time: { + defaultMode: 'fixed', + utcFormat: 'utc.day-of-year', + lmstEpoch: Date.UTC(2020, 2, 18, 0, 0, 0), + subscriptionMCWSFilterDelay: 100, + timeSystems: ['scet', 'ert'], + allowRealtime: true, + allowLAD: true, + records: 10 + }, + sessionHistoricalMaxResults: 100, + batchHistoricalChannelQueries: false, + disableSortParam: false, + messageStreamUrl: '', + messageTypeFilters: [], + frameAccountabilityExpectedVcidList: [], + queryTimespanLimit: undefined, + globalStalenessInterval: undefined, + customFormatters: [], + sessions: { + historicalSessionFilter: { + disable: false, + maxRecords: 100, + denyUnfilteredQueries: false + }, + realtimeSession: { + disable: false + } + }, + tablePerformanceOptions: { + telemetryMode: 'unlimited', + persistModeChange: false, + rowLimit: 50 + }, + assetPath: 'node_modules/openmct/dist' + }; + + // Deep merge function + function deepMerge(target, source) { + const output = Object.assign({}, target); + + if (isObject(target) && isObject(source)) { + Object.keys(source).forEach(key => { + if (isObject(source[key])) { + if (!(key in target)) { + Object.assign(output, { [key]: source[key] }); + } else { + output[key] = deepMerge(target[key], source[key]); + } + } else { + Object.assign(output, { [key]: source[key] }); + } + }); + } + + return output; + } + + function isObject(item) { + return item && typeof item === 'object' && !Array.isArray(item); + } + + const config = deepMerge(defaultConfig, options || {}); + + let persistenceLoaded; + const persistenceLoadedPromise = new Promise((resolve) => { + persistenceLoaded = resolve; + }); + + openmct.setAssetPath(config.assetPath); + openmct.install(ClearDataIndicatorPlugin(config.globalStalenessInterval)); + openmct.install(CommandEventsViewPlugin(config.tablePerformanceOptions)); + openmct.install(MessagesPlugin(config.tablePerformanceOptions)); + openmct.install(ProductStatusPlugin(config.tablePerformanceOptions)); + openmct.install(MetadataActionPlugin()); + openmct.install(DictionaryViewPlugin(config.tablePerformanceOptions)); + openmct.install(PacketSummaryPlugin(config.tablePerformanceOptions)); + openmct.install(ContainerViewPlugin()); + + // initialize session service, datasetCache service, global staleness + SessionService(openmct, config); + DatasetCache(openmct); + GlobalStaleness(openmct, config.globalStalenessInterval); + + openmct.install(new FormatPlugin(config)); + + const timePlugin = new TimePlugin(config.time); + + openmct.install(timePlugin); + + const formatKey = config.time.utcFormat; + const utcFormat = formatKey + ? openmct.telemetry.getFormatter(formatKey) + : new UTCDayOfYearFormat(); + const vistaTime = getVistaTime({ + options: timePlugin, + format: utcFormat + }); + openmct.install(RealtimeIndicatorPlugin(vistaTime, utcFormat)); + + mcwsClient.configure(config); + + openmct.install(MultipleHistoricalSessions(config.tablePerformanceOptions)); + openmct.install(RealtimeSessions()); + openmct.install(new HistoricalTelemetryPlugin(config)); + openmct.install(new RealtimeTelemetryPlugin(vistaTime, config)); + openmct.install(new TypePlugin()); + openmct.install(new TaxonomyPlugin(config.taxonomy)); + openmct.install(new LinkPlugin(config)); + openmct.install(new VenuePlugin(config)); + openmct.install(FrameWatchViewPlugin(config.tablePerformanceOptions)); + openmct.install(FrameEventFilterViewPlugin(config.tablePerformanceOptions)); + openmct.install(new ChannelTablePlugin(config.tablePerformanceOptions)); + openmct.install(new ChannelTableSetPlugin()); + openmct.install(new ChannelLimitsPlugin()); + openmct.install(new FrameAccountabilityPlugin(config)); + openmct.install(EVRViewPlugin(config)); + openmct.install(new AlarmsViewPlugin(config.tablePerformanceOptions)); + openmct.install(MCWSIndicatorPlugin()); + + if (config.messageStreamUrl && config.messageStreamUrl !== '') { + openmct.install( + new MessageStreamProcessor(config.messageStreamUrl, { + clearData: ['StartOfSession', 'EndOfSession'], + suspectChannels: ['SuspectChannels'] + }) + ); + } + + if (config.customFormatters.length) { + openmct.install(CustomFormatterPlugin(config.customFormatters)); + } + + openmct.install(CustomFormsPlugin()); + openmct.install( + new ExportDataAction([ + 'table', + 'telemetry.plot.overlay', + 'telemetry.plot.stacked', + 'vista.channel', + 'vista.channelGroup', + 'vista.chanTableGroup', + 'vista.evr', + 'vista.evrModule', + 'vista.evrSource', + 'vista.evrView' + ]) + ); + openmct.install(ActionModifiersPlugin()); + openmct.install(new PacketQueryPlugin()); + + if (config.globalFilters) { + openmct.install(new GlobalFilters(config.globalFilters)); + } + + openmct.user.setProvider(new IdentityProvider(openmct)); + + if (config.useDeveloperStorage) { + // plugins installed in recipe + persistenceLoaded(); + } else { + const mcwsPersistenceProvider = MCWSPersistenceProviderPlugin(config.namespaces); + + openmct.install(async (_openmct) => { + await mcwsPersistenceProvider(_openmct); + persistenceLoaded(); + }); + } + + + + openmct.branding({ aboutHtml: insertBuildInfo(AboutTemplate) }); + + // do not show telemetry if it falls out of bounds + // even if there is no new telemetry + openmct.telemetry.greedyLAD(false); + + persistenceLoadedPromise.then(() => { + window.openmctMCWSConfig = config; + // window.openmct = openmct; + }); + } + + /** + * Replaces placeholders in the HTML with build info provided by webpack. + * Build info is defined in webpack config, and is exposed as global + * JavaScript variables + * @param {*} markup + */ + function insertBuildInfo(markup) { + return markup + .replace(/\$\{project\.version\}/g, __OMM_VERSION__) + .replace(/\$\{timestamp\}/g, __OMM_BUILD_DATE__) + .replace(/\$\{buildNumber\}/g, __OMM_REVISION__) + .replace(/\$\{branch\}/g, __OMM_BUILD_BRANCH__); + } +} diff --git a/recipes/default.yaml b/recipes/default.yaml new file mode 100644 index 0000000..ee9a0c8 --- /dev/null +++ b/recipes/default.yaml @@ -0,0 +1,52 @@ +# yaml-language-server: $schema=../src/assets/openmct-configuration-schema.json +openmct: + version: '4.1.0' + plugins: + - openmct.plugins.Snow # Theme: 'Snow', 'Espresso' or 'Maelstrom' + - openmct.plugins.ObjectMigration + - openmct.plugins.ClearData: + options: + - ['table', 'telemetry.plot.overlay', 'telemetry.plot.stacked', 'vista.packetSummaryEvents', 'vista.dataProducts', 'vista.packets', 'vista.frameSummary', 'vista.frameWatch', 'vista.chanTableGroup'] + - indicator: false + - openmct.plugins.DisplayLayout: + options: + showAsView: + - summary-widget + - vista.packetSummaryEvents + - vista.dataProducts + - vista.packets + - vista.frameSummary + - vista.frameWatch + - openmct.plugins.Filters: + options: + - - vista.alarmsView + - telemetry.plot.overlay + - table + - vista.chanTableGroup + - vista.commandEventsView + - vista.messagesView + - vista.evrView + - openmct.plugins.UTCTimeSystem + - openmct.plugins.Notebook + - openmct.plugins.Clock: + options: + useClockIndicator: false + - openmct.plugins.DefaultRootName: + options: ['VISTA'] + # Open MCT for MCWS Plugin + - openmct-mcws-plugin: + options: + camUrl: '' + mcwsUrl: '' + namespaces: + - key: 'r50-dev' + name: 'R5.0 Shared' + url: '' + - userNamespace: true + key: 'r50-dev' + name: 'R5.0 Users' + url: '' + # Optional Dev Plugins + # - openmct.plugins.LocalStorage + # - openmct.plugins.MyItems + # Open MCT for MCWS Plugin Options \ No newline at end of file diff --git a/recipes/development.yaml b/recipes/development.yaml new file mode 100644 index 0000000..2e7a4d9 --- /dev/null +++ b/recipes/development.yaml @@ -0,0 +1,56 @@ +# yaml-language-server: $schema=../src/assets/openmct-configuration-schema.json +openmct: + version: 'latest' + plugins: + - openmct.plugins.Snow # Theme: 'Snow', 'Espresso' or 'Maelstrom' + - openmct.plugins.ObjectMigration + - openmct.plugins.ClearData: + options: + - ['table', 'telemetry.plot.overlay', 'telemetry.plot.stacked', 'vista.packetSummaryEvents', 'vista.dataProducts', 'vista.packets', 'vista.frameSummary', 'vista.frameWatch', 'vista.chanTableGroup'] + - indicator: false + - openmct.plugins.DisplayLayout: + options: + showAsView: + - summary-widget + - vista.packetSummaryEvents + - vista.dataProducts + - vista.packets + - vista.frameSummary + - vista.frameWatch + - openmct.plugins.Filters: + options: + - - vista.alarmsView + - telemetry.plot.overlay + - table + - vista.chanTableGroup + - vista.commandEventsView + - vista.messagesView + - vista.evrView + - openmct.plugins.UTCTimeSystem + - openmct.plugins.Notebook + - openmct.plugins.Clock: + options: + useClockIndicator: false + - openmct.plugins.DefaultRootName: + options: ['VISTA'] + # Open MCT for MCWS Plugin + - openmct-mcws-plugin: + npmPackage: file:/Users/jjviglio/OpenMCT/OMM/openmct-mcws/ + entryPoint: dist/openmct-mcws-plugin.js + options: + useDeveloperStorage: true + proxyUrl: 'http://localhost:8080/' + camUrl: '' + mcwsUrl: '' + namespaces: + - key: 'r50-dev' + name: 'R5.0 Shared' + url: '' + - userNamespace: true + key: 'r50-dev' + name: 'R5.0 Users' + url: '' + # Required core plugins for development + - openmct.plugins.LocalStorage + - openmct.plugins.MyItems + - openmct.plugins.example.Generator \ No newline at end of file diff --git a/serve-instance.js b/serve-instance.js new file mode 100755 index 0000000..fe9eaa4 --- /dev/null +++ b/serve-instance.js @@ -0,0 +1,173 @@ +#!/usr/bin/env node + +/** + * Simple server to serve OpenMCT instances with proxy support + * Replicates webpack-dev-server proxy configuration + * + * Usage: node serve-instance.js [instance-name] [port] + * Example: node serve-instance.js development 8080 + */ + +import express from 'express'; +import { createProxyMiddleware } from 'http-proxy-middleware'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import { dirname } from 'path'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const instanceName = process.argv[2] || 'development'; +const port = parseInt(process.argv[3] || '8080', 10); +const instancePath = path.join(__dirname, 'instances', instanceName); + +// Proxy configuration (matching webpack.dev.js) +const proxyUrl = process.env.PROXY_URL || 'http://localhost:8080'; +const apiUrl = process.env.API_URL || ''; +const proxyHeaders = {}; + +if (process.env.COOKIE) { + proxyHeaders.Cookie = process.env.COOKIE; +} + +const app = express(); + +// Proxy: /mcws-test -> http://localhost:8090 +app.use('/mcws-test', createProxyMiddleware({ + target: 'http://localhost:8090', + changeOrigin: true, + secure: false, + logLevel: 'debug' +})); + +// Proxy: /mcws -> apiUrl +if (apiUrl) { + app.use('/mcws', createProxyMiddleware({ + target: apiUrl, + changeOrigin: true, + secure: false, + headers: proxyHeaders, + logLevel: 'debug' + })); +} + +// Proxy: /proxyUrl -> extract URL from query parameter and proxy to it +// This replicates webpack-dev-server behavior where pathRewrite returns the full URL +app.use('/proxyUrl', express.raw({ type: '*/*', limit: '50mb' }), async (req, res, next) => { + let targetUrl = null; + + // Extract the 'url' parameter from the query string + // We need to handle this carefully because the url parameter itself may contain query parameters + const rawQuery = req.originalUrl?.split('?')[1] || req.url?.split('?')[1]; + + if (rawQuery) { + // Find the 'url=' parameter - it should be first + // Extract everything from 'url=' until the first unencoded '&' (or end of string) + const urlMatch = rawQuery.match(/^url=(.+?)(?:&|$)/); + if (urlMatch) { + try { + const decodedUrl = decodeURIComponent(urlMatch[1]); + console.log('Decoded URL from query:', decodedUrl); + + // Handle relative URLs or query strings (like webpack dev server does) + if (decodedUrl.startsWith('http://') || decodedUrl.startsWith('https://')) { + targetUrl = decodedUrl; + } else if (decodedUrl.startsWith('?')) { + // Query string only - use request origin as base (like webpack dev server) + const origin = `${req.protocol}://${req.get('host')}`; + targetUrl = origin + decodedUrl; + } else if (decodedUrl.startsWith('/')) { + // Absolute path - use request origin as base + const origin = `${req.protocol}://${req.get('host')}`; + targetUrl = origin + decodedUrl; + } else { + // Relative path - use request origin + path + const origin = `${req.protocol}://${req.get('host')}`; + targetUrl = origin + '/' + decodedUrl; + } + } catch (e) { + // Invalid URL encoding + console.error('Error decoding URL:', e, 'Raw value:', urlMatch[1]); + targetUrl = null; + } + } + } + + if (!targetUrl) { + console.error('Failed to extract valid URL from request:', req.originalUrl || req.url); + return res.status(400).send('Missing or invalid url query parameter. The url parameter must be a valid HTTP/HTTPS URL.'); + } + + try { + console.log('Generic URL Proxy to:', targetUrl); + + // Prepare headers - exclude headers that shouldn't be forwarded + const headersToForward = { ...proxyHeaders }; + const headersToExclude = ['host', 'connection', 'content-length', 'transfer-encoding']; + for (const [key, value] of Object.entries(req.headers)) { + if (!headersToExclude.includes(key.toLowerCase())) { + headersToForward[key] = value; + } + } + + // Prepare fetch options + const fetchOptions = { + method: req.method, + headers: headersToForward + }; + + // Forward request body for non-GET/HEAD requests + if (req.method !== 'GET' && req.method !== 'HEAD' && req.body) { + fetchOptions.body = req.body; + } + + // Make a request to the target URL + const response = await fetch(targetUrl, fetchOptions); + + // Forward response headers + response.headers.forEach((value, key) => { + // Exclude headers that Express handles + if (key.toLowerCase() !== 'content-encoding') { + res.setHeader(key, value); + } + }); + + // Set status code + res.status(response.status); + + // Forward response body + const buffer = await response.arrayBuffer(); + res.send(Buffer.from(buffer)); + } catch (e) { + console.error('Proxy error:', e); + if (!res.headersSent) { + res.status(500).send('Proxy error: ' + e.message); + } + } +}); + +// Serve static files from instance directory +app.use(express.static(instancePath)); + +// Serve node_modules/openmct/dist if it exists +const openmctDistPath = path.join(instancePath, 'node_modules', 'openmct', 'dist'); +app.use('/node_modules/openmct/dist', express.static(openmctDistPath)); + +// Serve test_data directory (matching webpack dev server configuration) +const testDataPath = path.join(__dirname, 'test_data'); +app.use('/test_data', express.static(testDataPath)); + +app.listen(port, () => { + console.log(`Serving instance "${instanceName}" at http://localhost:${port}`); + console.log(`Instance path: ${instancePath}`); + console.log(`Proxy configuration:`); + console.log(` /mcws-test -> http://localhost:8090`); + if (apiUrl) { + console.log(` /mcws -> ${apiUrl}`); + } + console.log(` /proxyUrl -> ${proxyUrl} (with query.url rewriting)`); + if (Object.keys(proxyHeaders).length > 0) { + console.log(` Proxy headers:`, proxyHeaders); + } +}); + diff --git a/src/AMMOSPlugins.js b/src/AMMOSPlugins.js deleted file mode 100644 index b2c1642..0000000 --- a/src/AMMOSPlugins.js +++ /dev/null @@ -1,155 +0,0 @@ -define([ - 'services/dataset/DatasetCache', - 'services/session/SessionService', - 'services/globalStaleness/globalStaleness', - './types/plugin', - './taxonomy/plugin', - './time/plugin', - 'services/time/vistaTime', - './historical/plugin', - './realtime/plugin', - './link/plugin', - './formats/plugin', - './venues/plugin', - 'services/mcws/MCWSClient', - './formats/UTCDayOfYearFormat', - './frameSummary/plugin', - './frameEventFilterView/plugin', - './channelTable/channelTablePlugin/plugin', - './channelTable/channelTableSetPlugin/plugin', - './channelLimits/plugin', - './frameAccountability/plugin', - './alarmsView/plugin', - './messageStreamProcessor/plugin', - './evrView/plugin', - './customFormatter/plugin', - './customForms/plugin', - './actionModifiers/plugin', - './realtimeIndicator/plugin', - './packetQuery/plugin', - './mcwsIndicator/plugin', - './multipleHistoricalSessions/plugin', - './realtimeSessions/plugin', - './globalFilters/plugin', - './exportDataAction/plugin' -], function ( - DatasetCache, - SessionService, - GlobalStaleness, - TypePlugin, - TaxonomyPlugin, - TimePlugin, - getVistaTime, - HistoricalTelemetryPlugin, - RealtimeTelemetryPlugin, - LinkPlugin, - FormatPlugin, - VenuePlugin, - mcwsClient, - UTCDayOfYearFormat, - FrameWatchViewPlugin, - FrameEventFilterViewPlugin, - ChannelTablePlugin, - ChannelTableSetPlugin, - ChannelLimitsPlugin, - FrameAccountabilityPlugin, - AlarmsViewPlugin, - MessageStreamProcessor, - EVRViewPlugin, - CustomFormatterPlugin, - CustomFormsPlugin, - ActionModifiersPlugin, - RealtimeIndicatorPlugin, - PacketQueryPlugin, - MCWSIndicatorPlugin, - MultipleHistoricalSessions, - RealtimeSessions, - GlobalFilters, - ExportDataAction -) { - function AMMOSPlugins(options) { - return function install(openmct) { - // initialize session service, datasetCache service, global staleness - SessionService.default(openmct, options); - DatasetCache.default(openmct); - GlobalStaleness.default(openmct, options.globalStalenessInterval); - - openmct.install(new FormatPlugin(options)); - - const timePlugin = new TimePlugin.default(options.time); - openmct.install(timePlugin); - - const formatKey = options.time.utcFormat; - const utcFormat = formatKey - ? openmct.telemetry.getFormatter(formatKey) - : new UTCDayOfYearFormat.default(); - const vistaTime = getVistaTime.default({ - options: timePlugin, - format: utcFormat - }); - openmct.install(RealtimeIndicatorPlugin.default(vistaTime, utcFormat)); - - mcwsClient.default.configure(options); - - openmct.install(MultipleHistoricalSessions.default(options.tablePerformanceOptions)); - openmct.install(RealtimeSessions.default()); - - openmct.install(new HistoricalTelemetryPlugin(options)); - openmct.install(new RealtimeTelemetryPlugin.default(vistaTime, options)); - openmct.install(new TypePlugin.default()); - openmct.install(new TaxonomyPlugin(options.taxonomy)); - openmct.install(new LinkPlugin(options)); - openmct.install(new VenuePlugin.default(options)); - openmct.install(FrameWatchViewPlugin.default(options.tablePerformanceOptions)); - openmct.install(FrameEventFilterViewPlugin.default(options.tablePerformanceOptions)); - openmct.install(new ChannelTablePlugin.default(options.tablePerformanceOptions)); - openmct.install(new ChannelTableSetPlugin.default()); - openmct.install(new ChannelLimitsPlugin.default()); - openmct.install(new FrameAccountabilityPlugin.default(options)); - openmct.install(EVRViewPlugin.default(options)); - openmct.install(new AlarmsViewPlugin.default(options.tablePerformanceOptions)); - openmct.install(MCWSIndicatorPlugin.default()); - - if ( - window.openmctMCWSConfig.messageStreamUrl && - window.openmctMCWSConfig.messageStreamUrl !== '' - ) { - openmct.install( - new MessageStreamProcessor(window.openmctMCWSConfig.messageStreamUrl, { - clearData: ['StartOfSession', 'EndOfSession'], - suspectChannels: ['SuspectChannels'] - }) - ); - } - - if (options.customFormatters.length) { - openmct.install(CustomFormatterPlugin.default(options.customFormatters)); - } - - openmct.install(CustomFormsPlugin.default()); - - openmct.install(openmct.plugins.DefaultRootName('VISTA')); - openmct.install( - new ExportDataAction.default([ - 'table', - 'telemetry.plot.overlay', - 'telemetry.plot.stacked', - 'vista.channel', - 'vista.channelGroup', - 'vista.chanTableGroup', - 'vista.evr', - 'vista.evrModule', - 'vista.evrSource', - 'vista.evrView' - ]) - ); - openmct.install(ActionModifiersPlugin.default()); - openmct.install(new PacketQueryPlugin.default()); - if (options.globalFilters) { - openmct.install(new GlobalFilters.default(options.globalFilters)); - } - }; - } - - return AMMOSPlugins; -}); diff --git a/src/actionModifiers/ImageExport/ImageExportModifier.js b/src/actionModifiers/ImageExport/ImageExportModifier.js index 2737055..0bffd90 100644 --- a/src/actionModifiers/ImageExport/ImageExportModifier.js +++ b/src/actionModifiers/ImageExport/ImageExportModifier.js @@ -1,5 +1,5 @@ -import SessionService from 'services/session/SessionService'; -import { formatNumberSequence } from 'ommUtils/strings'; +import SessionService from 'services/session/SessionService.js'; +import { formatNumberSequence } from 'ommUtils/strings.js'; function imageExportModifier(openmct) { const PNGImageExportAction = openmct.actions._allActions['export-as-png']; diff --git a/src/actionModifiers/ImportExportWithDatasets/importWithDatasetsModifier.js b/src/actionModifiers/ImportExportWithDatasets/importWithDatasetsModifier.js index ea98992..5f8937c 100644 --- a/src/actionModifiers/ImportExportWithDatasets/importWithDatasetsModifier.js +++ b/src/actionModifiers/ImportExportWithDatasets/importWithDatasetsModifier.js @@ -1,5 +1,5 @@ import ImportWithDatasetsFormComponent from './ImportWithDatasetsFormComponent.vue'; -import mount from 'ommUtils/mountVueComponent'; +import mount from 'ommUtils/mountVueComponent.js'; import DatasetCache from 'services/dataset/DatasetCache'; import Types from 'types/types'; diff --git a/src/actionModifiers/MultipleDatasets/warnMultipleDatasetsOnDuplicateModifier.js b/src/actionModifiers/MultipleDatasets/warnMultipleDatasetsOnDuplicateModifier.js index 084a9a3..1f3db72 100644 --- a/src/actionModifiers/MultipleDatasets/warnMultipleDatasetsOnDuplicateModifier.js +++ b/src/actionModifiers/MultipleDatasets/warnMultipleDatasetsOnDuplicateModifier.js @@ -1,4 +1,4 @@ -import { MULTIPLE_DATASET_WARNING } from './constants'; +import { MULTIPLE_DATASET_WARNING } from './constants.js'; export default function warnMultipleDatasetsOnDuplicateModifier(openmct) { const duplicateAction = openmct.actions._allActions['duplicate']; diff --git a/src/actionModifiers/MultipleDatasets/warnMultipleDatasetsOnImportModifier.js b/src/actionModifiers/MultipleDatasets/warnMultipleDatasetsOnImportModifier.js index d1a2437..c8dfad7 100644 --- a/src/actionModifiers/MultipleDatasets/warnMultipleDatasetsOnImportModifier.js +++ b/src/actionModifiers/MultipleDatasets/warnMultipleDatasetsOnImportModifier.js @@ -1,5 +1,5 @@ -import DatasetCache from 'services/dataset/DatasetCache'; -import { MULTIPLE_DATASET_WARNING } from './constants'; +import DatasetCache from 'services/dataset/DatasetCache.js'; +import { MULTIPLE_DATASET_WARNING } from './constants.js'; export default function warnMultipleDatasetsOnImportModifier(openmct) { const importAsJSONAction = openmct.actions._allActions['import.JSON']; diff --git a/src/actionModifiers/plugin.js b/src/actionModifiers/plugin.js index 4b42ec6..f0fb6c9 100644 --- a/src/actionModifiers/plugin.js +++ b/src/actionModifiers/plugin.js @@ -1,7 +1,7 @@ -import preventImportIntoDatasetModifier from './preventImportIntoDatasetModifier'; -import importWithDatasetsModifier from './ImportExportWithDatasets/importWithDatasetsModifier'; -import warnMultipleDatasetsOnDuplicateModifier from './MultipleDatasets/warnMultipleDatasetsOnDuplicateModifier'; -import imageExportModifier from './ImageExport/ImageExportModifier'; +import preventImportIntoDatasetModifier from './preventImportIntoDatasetModifier.js'; +import importWithDatasetsModifier from './ImportExportWithDatasets/importWithDatasetsModifier.js'; +import warnMultipleDatasetsOnDuplicateModifier from './MultipleDatasets/warnMultipleDatasetsOnDuplicateModifier.js'; +import imageExportModifier from './ImageExport/ImageExportModifier.js'; /** * DEPENDENCY: These modifiers have a dependency on Open MCT action internals. diff --git a/src/alarmsView/AlarmsAutoclearViewProvider.js b/src/alarmsView/AlarmsAutoclearViewProvider.js index 666e311..e0a052c 100644 --- a/src/alarmsView/AlarmsAutoclearViewProvider.js +++ b/src/alarmsView/AlarmsAutoclearViewProvider.js @@ -1,4 +1,4 @@ -import mount from 'ommUtils/mountVueComponent'; +import mount from 'ommUtils/mountVueComponent.js'; import AlarmsAutoclear from './AlarmsAutoclear.vue'; import TelemetryTableConfiguration from 'openmct.tables.TelemetryTableConfiguration'; diff --git a/src/alarmsView/AlarmsTable.js b/src/alarmsView/AlarmsTable.js index 24935d1..95d5324 100644 --- a/src/alarmsView/AlarmsTable.js +++ b/src/alarmsView/AlarmsTable.js @@ -1,7 +1,7 @@ import _ from 'lodash'; import TelemetryTable from 'openmct.tables.TelemetryTable'; -import AlarmsViewRowCollection from './AlarmsViewRowCollection'; -import AlarmsViewHistoricalContextTableRow from './AlarmsViewHistoricalContextTableRow'; +import AlarmsViewRowCollection from './AlarmsViewRowCollection.js'; +import AlarmsViewHistoricalContextTableRow from './AlarmsViewHistoricalContextTableRow.js'; export default class AlarmsTable extends TelemetryTable { initialize() { diff --git a/src/alarmsView/AlarmsViewProvider.js b/src/alarmsView/AlarmsViewProvider.js index cb9845e..e34daee 100644 --- a/src/alarmsView/AlarmsViewProvider.js +++ b/src/alarmsView/AlarmsViewProvider.js @@ -1,4 +1,4 @@ -import mount from 'ommUtils/mountVueComponent'; +import mount from 'ommUtils/mountVueComponent.js'; import TableComponent from 'openmct.tables.components.Table'; import AlarmsTable from './AlarmsTable.js'; diff --git a/src/alarmsView/plugin.js b/src/alarmsView/plugin.js index 707316f..0a5f321 100644 --- a/src/alarmsView/plugin.js +++ b/src/alarmsView/plugin.js @@ -1,7 +1,7 @@ -import AlarmsViewProvider from './AlarmsViewProvider'; -import AlarmsAutoclearViewProvider from './AlarmsAutoclearViewProvider'; -import AlarmsViewActions from './AlarmsViewActions'; -import VistaTableConfigurationProvider from '../tables/VistaTableConfigurationProvider'; +import AlarmsViewProvider from './AlarmsViewProvider.js'; +import AlarmsAutoclearViewProvider from './AlarmsAutoclearViewProvider.js'; +import AlarmsViewActions from './AlarmsViewActions.js'; +import VistaTableConfigurationProvider from '../tables/VistaTableConfigurationProvider.js'; export default function AlarmsViewPlugin(options) { return function install(openmct) { diff --git a/src/channelLimits/plugin.js b/src/channelLimits/plugin.js index ed1b55a..340c3f1 100644 --- a/src/channelLimits/plugin.js +++ b/src/channelLimits/plugin.js @@ -1,4 +1,4 @@ -import ChannelLimitsProvider from './ChannelLimitsProvider'; +import ChannelLimitsProvider from './ChannelLimitsProvider.js'; export default function ChannelLimitsPlugin() { return function install(openmct) { diff --git a/src/channelLimits/pluginSpec.js b/src/channelLimits/pluginSpec.js index 4ef5c2f..dba82d6 100644 --- a/src/channelLimits/pluginSpec.js +++ b/src/channelLimits/pluginSpec.js @@ -1,4 +1,4 @@ -import ChannelLimitsProvider from './ChannelLimitsProvider'; +import ChannelLimitsProvider from './ChannelLimitsProvider.js'; describe('the channel limits provider', () => { let channelLimitsProvider; diff --git a/src/channelTable/channelTablePlugin/ChannelTable.js b/src/channelTable/channelTablePlugin/ChannelTable.js index 41a4a0d..5fafa7f 100644 --- a/src/channelTable/channelTablePlugin/ChannelTable.js +++ b/src/channelTable/channelTablePlugin/ChannelTable.js @@ -1,9 +1,9 @@ -import ChannelTableRowCollection from './ChannelTableRowCollection'; -import ChannelTableRow from './ChannelTableRow'; +import ChannelTableRowCollection from './ChannelTableRowCollection.js'; +import ChannelTableRow from './ChannelTableRow.js'; import TelemetryTable from 'openmct.tables.TelemetryTable'; import TelemetryTableColumn from 'openmct.tables.TelemetryTableColumn'; -import EmptyChannelTableRow from './EmptyChannelTableRow'; -import ObjectNameColumn from './ObjectNameColumn'; +import EmptyChannelTableRow from './EmptyChannelTableRow.js'; +import ObjectNameColumn from './ObjectNameColumn.js'; export default class ChannelTable extends TelemetryTable { constructor(domainObject, openmct, options) { diff --git a/src/channelTable/channelTablePlugin/ChannelTableFormatViewProvider.js b/src/channelTable/channelTablePlugin/ChannelTableFormatViewProvider.js index 37fe933..14189ac 100644 --- a/src/channelTable/channelTablePlugin/ChannelTableFormatViewProvider.js +++ b/src/channelTable/channelTablePlugin/ChannelTableFormatViewProvider.js @@ -1,6 +1,6 @@ import CellFormatConfigurationComponent from './CellFormatConfigurationComponent.vue'; import TelemetryTableConfiguration from 'openmct.tables.TelemetryTableConfiguration'; -import mount from 'ommUtils/mountVueComponent'; +import mount from 'ommUtils/mountVueComponent.js'; export default function ChannelTableFormatViewProvider(openmct, options) { return { diff --git a/src/channelTable/channelTablePlugin/ChannelTableViewProvider.js b/src/channelTable/channelTablePlugin/ChannelTableViewProvider.js index acff365..8617211 100644 --- a/src/channelTable/channelTablePlugin/ChannelTableViewProvider.js +++ b/src/channelTable/channelTablePlugin/ChannelTableViewProvider.js @@ -1,6 +1,6 @@ -import ChannelTable from './ChannelTable'; +import ChannelTable from './ChannelTable.js'; import TableComponent from 'openmct.tables.components.Table'; -import mount from 'ommUtils/mountVueComponent'; +import mount from 'ommUtils/mountVueComponent.js'; export default class ChannelTableViewProvider { constructor(openmct, options) { diff --git a/src/channelTable/channelTablePlugin/ObjectNameColumn.js b/src/channelTable/channelTablePlugin/ObjectNameColumn.js index 7f0c8cd..d83bb97 100644 --- a/src/channelTable/channelTablePlugin/ObjectNameColumn.js +++ b/src/channelTable/channelTablePlugin/ObjectNameColumn.js @@ -1,33 +1,31 @@ -define(function () { - class ObjectNameColumn { - constructor(objectName) { - this.objectName = objectName; - } +class ObjectNameColumn { + constructor(objectName) { + this.objectName = objectName; + } - getKey() { - return 'vista-lad-name'; - } + getKey() { + return 'vista-lad-name'; + } - getTitle() { - return 'Name'; - } + getTitle() { + return 'Name'; + } - getMetadatum() { - return {}; - } + getMetadatum() { + return {}; + } - hasValueForDatum() { - return true; - } + hasValueForDatum() { + return true; + } - getRawValue() { - return this.objectName; - } + getRawValue() { + return this.objectName; + } - getFormattedValue() { - return this.objectName; - } + getFormattedValue() { + return this.objectName; } +} - return ObjectNameColumn; -}); +export default ObjectNameColumn; diff --git a/src/channelTable/channelTablePlugin/plugin.js b/src/channelTable/channelTablePlugin/plugin.js index fee8d3b..6e8c061 100644 --- a/src/channelTable/channelTablePlugin/plugin.js +++ b/src/channelTable/channelTablePlugin/plugin.js @@ -3,10 +3,10 @@ import { CHANNEL_TABLE_NAME, CHANNEL_TABLE_ICON, CHANNEL_KEY -} from '../constants'; -import ChannelTableViewProvider from './ChannelTableViewProvider'; -import ChannelTableFormatViewProvider from './ChannelTableFormatViewProvider'; -import VistaTableConfigurationProvider from '../../tables/VistaTableConfigurationProvider'; +} from '../constants.js'; +import ChannelTableViewProvider from './ChannelTableViewProvider.js'; +import ChannelTableFormatViewProvider from './ChannelTableFormatViewProvider.js'; +import VistaTableConfigurationProvider from '../../tables/VistaTableConfigurationProvider.js'; export default function install(options) { return function ChannelTablePlugin(openmct) { diff --git a/src/channelTable/channelTableSetPlugin/ChannelTableSetCompositionPolicy.js b/src/channelTable/channelTableSetPlugin/ChannelTableSetCompositionPolicy.js index 169f015..b119f68 100644 --- a/src/channelTable/channelTableSetPlugin/ChannelTableSetCompositionPolicy.js +++ b/src/channelTable/channelTableSetPlugin/ChannelTableSetCompositionPolicy.js @@ -1,4 +1,4 @@ -import { CHANNEL_TABLE_KEY, CHANNEL_TABLE_SET_KEY } from '../constants'; +import { CHANNEL_TABLE_KEY, CHANNEL_TABLE_SET_KEY } from '../constants.js'; export default function channelTableSetCompositionPolicy(openmct) { return function (parent, child) { diff --git a/src/channelTable/channelTableSetPlugin/ChannelTableSetView.js b/src/channelTable/channelTableSetPlugin/ChannelTableSetView.js index 2dd5d81..051e0a1 100644 --- a/src/channelTable/channelTableSetPlugin/ChannelTableSetView.js +++ b/src/channelTable/channelTableSetPlugin/ChannelTableSetView.js @@ -1,5 +1,5 @@ import ChannelTableSet from './ChannelTableSet.vue'; -import mount from 'ommUtils/mountVueComponent'; +import mount from 'ommUtils/mountVueComponent.js'; export default class ChannelTableSetView { constructor(openmct, domainObject, objectPath) { diff --git a/src/channelTable/channelTableSetPlugin/ChannelTableSetViewProvider.js b/src/channelTable/channelTableSetPlugin/ChannelTableSetViewProvider.js index d25cbd0..2b3f76e 100644 --- a/src/channelTable/channelTableSetPlugin/ChannelTableSetViewProvider.js +++ b/src/channelTable/channelTableSetPlugin/ChannelTableSetViewProvider.js @@ -3,7 +3,7 @@ import { CHANNEL_TABLE_SET_NAME, CHANNEL_TABLE_SET_VIEW_KEY, CHANNEL_TABLE_SET_KEY -} from '../constants'; +} from '../constants.js'; import ChannelTableSetView from './ChannelTableSetView.js'; export default class ChannelTableSetViewProvider { diff --git a/src/channelTable/channelTableSetPlugin/plugin.js b/src/channelTable/channelTableSetPlugin/plugin.js index 2ef53a6..575d07d 100644 --- a/src/channelTable/channelTableSetPlugin/plugin.js +++ b/src/channelTable/channelTableSetPlugin/plugin.js @@ -2,9 +2,9 @@ import { CHANNEL_TABLE_SET_ICON, CHANNEL_TABLE_SET_KEY, CHANNEL_TABLE_SET_NAME -} from '../constants'; -import ChannelTableSetViewProvider from './ChannelTableSetViewProvider'; -import channelTableSetCompositionPolicy from './ChannelTableSetCompositionPolicy'; +} from '../constants.js'; +import ChannelTableSetViewProvider from './ChannelTableSetViewProvider.js'; +import channelTableSetCompositionPolicy from './ChannelTableSetCompositionPolicy.js'; export default function ChannelTableSetPlugin() { return function install(openmct) { diff --git a/src/clearDataIndicator/plugin.js b/src/clearDataIndicator/plugin.js index 01d2f49..92d0ecf 100644 --- a/src/clearDataIndicator/plugin.js +++ b/src/clearDataIndicator/plugin.js @@ -1,4 +1,4 @@ -import mount from 'ommUtils/mountVueComponent'; +import mount from 'ommUtils/mountVueComponent.js'; import ClearDataIndicator from './ClearDataIndicator.vue'; export default function plugin(globalStalenessMs) { diff --git a/src/commandEventsView/CommandEventsViewProvider.js b/src/commandEventsView/CommandEventsViewProvider.js index ce387c7..0177e06 100644 --- a/src/commandEventsView/CommandEventsViewProvider.js +++ b/src/commandEventsView/CommandEventsViewProvider.js @@ -1,6 +1,6 @@ import CommandEventsTable from './CommandEventsTable.js'; import TableComponent from 'openmct.tables.components.Table'; -import mount from 'ommUtils/mountVueComponent'; +import mount from 'ommUtils/mountVueComponent.js'; export default class CommandEventsViewProvider { constructor(openmct, options) { diff --git a/src/constants.js b/src/constants.js index 6d85466..7ae9e03 100644 --- a/src/constants.js +++ b/src/constants.js @@ -1,217 +1,215 @@ -define([], function () { - var CONSTANTS = { - DICTIONARY_PROPERTIES: [ - 'channelDictionaryUrl', - 'channelEnumerationDictionaryUrl', - 'eventRecordDictionaryUrl' - ], - /** - * An array of model properties that provide evrs. - */ - EVR_PROPERTIES: [ - 'eventRecordDictionaryUrl', - 'evrHistoricalUrl', - 'evrSimStreamUrl', - 'evrSimStreamDatatableUrl', - 'evrStreamUrl', - 'evrLADUrl' - ], - /** - * An array of model properties that provide channels. - */ - CHANNEL_PROPERTIES: ['channelDictionaryUrl'], +const CONSTANTS = { + DICTIONARY_PROPERTIES: [ + 'channelDictionaryUrl', + 'channelEnumerationDictionaryUrl', + 'eventRecordDictionaryUrl' + ], + /** + * An array of model properties that provide evrs. + */ + EVR_PROPERTIES: [ + 'eventRecordDictionaryUrl', + 'evrHistoricalUrl', + 'evrSimStreamUrl', + 'evrSimStreamDatatableUrl', + 'evrStreamUrl', + 'evrLADUrl' + ], + /** + * An array of model properties that provide channels. + */ + CHANNEL_PROPERTIES: ['channelDictionaryUrl'], - /** - * An array of model properties that provide different Types of channel - * telemetry. - */ - CHANNEL_TAXONOMY_PROPERTIES: [ - 'channelHistoricalUrl', - 'channelMinMaxUrl', - 'channelLADUrl', - 'channelSimStreamUrl', - 'channelSimStreamDatatableUrl', - 'channelStreamUrl' - ], + /** + * An array of model properties that provide different Types of channel + * telemetry. + */ + CHANNEL_TAXONOMY_PROPERTIES: [ + 'channelHistoricalUrl', + 'channelMinMaxUrl', + 'channelLADUrl', + 'channelSimStreamUrl', + 'channelSimStreamDatatableUrl', + 'channelStreamUrl' + ], - /** - * An array of model properties that provide data products. - */ - DATA_PRODUCT_PROPERTIES: ['dataProductUrl', 'dataProductContentUrl'], + /** + * An array of model properties that provide data products. + */ + DATA_PRODUCT_PROPERTIES: ['dataProductUrl', 'dataProductContentUrl'], - /** - * An array of model properties that provide packet metadata and contents. - * @type {string[]} - */ - PACKET_PROPERTIES: ['packetUrl', 'packetContentUrl'], + /** + * An array of model properties that provide packet metadata and contents. + * @type {string[]} + */ + PACKET_PROPERTIES: ['packetUrl', 'packetContentUrl'], - /** - * Ranges available on an EVR type. - */ - EVR_RANGES: [ - { - name: 'Record Type', - key: 'record_type', - format: 'string', - hints: {} - }, - { - name: 'Module', - key: 'module', - format: 'string', - hints: {} - }, - { - name: 'Level', - key: 'level', - format: 'string', - hints: {} - }, - { - name: 'EVR Name', - key: 'name', - format: 'string', - hints: {} - }, - { - name: 'Message', - key: 'message', - format: 'string', - hints: {} - }, - { - name: 'Metadata Values', - key: 'metadata_values', - format: 'string', - hints: {} - }, - { - name: 'Metadata keywords', - key: 'metadata_keywords', - format: 'string', - hints: {} - }, - { - name: 'Event ID', - key: 'event_id', - format: 'string', - hints: {} - }, - { - name: 'DSS ID', - key: 'dss_id', - format: 'integer', - hints: {} - }, - { - name: 'Realtime?', - key: 'realtime', - format: 'string', - hints: {}, - filters: [ - { - comparator: 'equals', - possibleValues: [ - { value: true, label: 'Realtime' }, - { value: false, label: 'Recorded' } - ] - } - ] - }, - { - name: 'Session ID', - key: 'session_id', - format: 'integer', - hints: {} - }, - { - name: 'Session Host', - key: 'session_host', - format: 'string', - hints: {} - }, - { - name: 'From SSE?', - key: 'from_sse', - format: 'string', - hints: {}, - filters: [ - { - comparator: 'equals', - possibleValues: [ - { value: true, label: 'SSE' }, - { value: false, label: 'FSW' } - ] - } - ] - }, - { - name: 'VCID', - key: 'vcid', - format: 'string', - hints: {} - }, - { - name: 'LST', - key: 'lst', - format: 'string', - hints: {} - }, - { - name: 'RCT', - key: 'rct', - format: 'string', - hints: {} - }, - { - key: 'isStale', - source: 'isStale', - name: 'Stale', - format: 'enum', - type: 'enum', - enumerations: [ - { - string: 'TRUE', - value: 1 - }, - { - string: 'FALSE', - value: 0 - } - ], - hints: { - range: 0 + /** + * Ranges available on an EVR type. + */ + EVR_RANGES: [ + { + name: 'Record Type', + key: 'record_type', + format: 'string', + hints: {} + }, + { + name: 'Module', + key: 'module', + format: 'string', + hints: {} + }, + { + name: 'Level', + key: 'level', + format: 'string', + hints: {} + }, + { + name: 'EVR Name', + key: 'name', + format: 'string', + hints: {} + }, + { + name: 'Message', + key: 'message', + format: 'string', + hints: {} + }, + { + name: 'Metadata Values', + key: 'metadata_values', + format: 'string', + hints: {} + }, + { + name: 'Metadata keywords', + key: 'metadata_keywords', + format: 'string', + hints: {} + }, + { + name: 'Event ID', + key: 'event_id', + format: 'string', + hints: {} + }, + { + name: 'DSS ID', + key: 'dss_id', + format: 'integer', + hints: {} + }, + { + name: 'Realtime?', + key: 'realtime', + format: 'string', + hints: {}, + filters: [ + { + comparator: 'equals', + possibleValues: [ + { value: true, label: 'Realtime' }, + { value: false, label: 'Recorded' } + ] } + ] + }, + { + name: 'Session ID', + key: 'session_id', + format: 'integer', + hints: {} + }, + { + name: 'Session Host', + key: 'session_host', + format: 'string', + hints: {} + }, + { + name: 'From SSE?', + key: 'from_sse', + format: 'string', + hints: {}, + filters: [ + { + comparator: 'equals', + possibleValues: [ + { value: true, label: 'SSE' }, + { value: false, label: 'FSW' } + ] + } + ] + }, + { + name: 'VCID', + key: 'vcid', + format: 'string', + hints: {} + }, + { + name: 'LST', + key: 'lst', + format: 'string', + hints: {} + }, + { + name: 'RCT', + key: 'rct', + format: 'string', + hints: {} + }, + { + key: 'isStale', + source: 'isStale', + name: 'Stale', + format: 'enum', + type: 'enum', + enumerations: [ + { + string: 'TRUE', + value: 1 + }, + { + string: 'FALSE', + value: 0 + } + ], + hints: { + range: 0 } - ], + } + ], - FRAME_EVENT_TYPES: { - BadTelemetryFrame: 'Bad Telemetry Frame', - InSync: 'In Sync.', - LossOfSync: 'Loss of Sync.', - FrameSequenceAnomaly: 'Frame Sequence Anomaly', - OutOfSyncData: 'Out Of Sync. Data' - }, + FRAME_EVENT_TYPES: { + BadTelemetryFrame: 'Bad Telemetry Frame', + InSync: 'In Sync.', + LossOfSync: 'Loss of Sync.', + FrameSequenceAnomaly: 'Frame Sequence Anomaly', + OutOfSyncData: 'Out Of Sync. Data' + }, - //Dataset fields that require websockets for realtime data - WEBSOCKET_STREAM_URL_KEYS: [ - 'channelStreamUrl', - 'dataProductStreamUrl', - 'evrStreamUrl', - 'messageStreamUrl', - 'frameSummaryStreamUrl', - 'alarmMessageStreamUrl', - 'frameEventStreamUrl' - ] - }; + //Dataset fields that require websockets for realtime data + WEBSOCKET_STREAM_URL_KEYS: [ + 'channelStreamUrl', + 'dataProductStreamUrl', + 'evrStreamUrl', + 'messageStreamUrl', + 'frameSummaryStreamUrl', + 'alarmMessageStreamUrl', + 'frameEventStreamUrl' + ] +}; - /** - * An array of model properties that is necessary to provide channel - * telemetry. - */ - CONSTANTS.CHANNEL_COPY_KEYS = CONSTANTS.CHANNEL_PROPERTIES.concat( - CONSTANTS.CHANNEL_TAXONOMY_PROPERTIES - ); +/** + * An array of model properties that is necessary to provide channel + * telemetry. + */ +CONSTANTS.CHANNEL_COPY_KEYS = CONSTANTS.CHANNEL_PROPERTIES.concat( + CONSTANTS.CHANNEL_TAXONOMY_PROPERTIES +); - return CONSTANTS; -}); +export default CONSTANTS; diff --git a/src/containerView/FolderGridViewProvider.js b/src/containerView/FolderGridViewProvider.js index 943e526..76b154c 100644 --- a/src/containerView/FolderGridViewProvider.js +++ b/src/containerView/FolderGridViewProvider.js @@ -1,5 +1,5 @@ import FolderGridViewComponent from 'openmct.views.FolderGridViewComponent'; -import mount from 'ommUtils/mountVueComponent'; +import mount from 'ommUtils/mountVueComponent.js'; export default class FolderGridView { constructor(openmct, types) { diff --git a/src/containerView/FolderListViewProvider.js b/src/containerView/FolderListViewProvider.js index f21a4d7..9d3f40d 100644 --- a/src/containerView/FolderListViewProvider.js +++ b/src/containerView/FolderListViewProvider.js @@ -1,5 +1,5 @@ import FolderListViewComponent from 'openmct.views.FolderListViewComponent'; -import mount from 'ommUtils/mountVueComponent'; +import mount from 'ommUtils/mountVueComponent.js'; export default class FolderListView { constructor(openmct, types) { diff --git a/src/containerView/plugin.js b/src/containerView/plugin.js index a959be5..fd53a60 100644 --- a/src/containerView/plugin.js +++ b/src/containerView/plugin.js @@ -1,6 +1,6 @@ -import FolderGridViewProvider from './FolderGridViewProvider'; -import FolderListViewProvider from './FolderListViewProvider'; -import BlankViewProvider from './BlankViewProvider'; +import FolderGridViewProvider from './FolderGridViewProvider.js'; +import FolderListViewProvider from './FolderListViewProvider.js'; +import BlankViewProvider from './BlankViewProvider.js'; const FOLDER_CONTAINER_TYPES = ['vista.dictionarySource']; diff --git a/src/customForms/UrlField/UrlFieldFormController.js b/src/customForms/UrlField/UrlFieldFormController.js index 45a0fae..5852332 100644 --- a/src/customForms/UrlField/UrlFieldFormController.js +++ b/src/customForms/UrlField/UrlFieldFormController.js @@ -1,5 +1,5 @@ import UrlField from './UrlField.vue'; -import mount from 'ommUtils/mountVueComponent'; +import mount from 'ommUtils/mountVueComponent.js'; export default function UrlFieldFormController(openmct) { let _destroy = null; diff --git a/src/customForms/plugin.js b/src/customForms/plugin.js index 2ec35e6..8fe9635 100644 --- a/src/customForms/plugin.js +++ b/src/customForms/plugin.js @@ -1,4 +1,4 @@ -import UrlFieldFormController from './UrlField/UrlFieldFormController'; +import UrlFieldFormController from './UrlField/UrlFieldFormController.js'; export default function CustomFormsPlugin() { return function install(openmct) { diff --git a/src/dictionaryView/dictionaryViewProvider.js b/src/dictionaryView/dictionaryViewProvider.js index 8b29d6f..3c617c3 100644 --- a/src/dictionaryView/dictionaryViewProvider.js +++ b/src/dictionaryView/dictionaryViewProvider.js @@ -1,6 +1,6 @@ import DictionaryView from './components/dictionaryView.vue'; import DictionaryViewTable from './dictionaryViewTable.js'; -import mount from 'ommUtils/mountVueComponent'; +import mount from 'ommUtils/mountVueComponent.js'; export default class DictionaryViewProvider { constructor(openmct, options) { diff --git a/src/dictionaryView/dictionaryViewTable.js b/src/dictionaryView/dictionaryViewTable.js index df5482e..b0b5ebd 100644 --- a/src/dictionaryView/dictionaryViewTable.js +++ b/src/dictionaryView/dictionaryViewTable.js @@ -1,6 +1,6 @@ import TelemetryTable from 'openmct.tables.TelemetryTable'; import TelemetryTableRow from 'openmct.tables.TelemetryTableRow'; -import mcws from 'services/mcws/mcws'; +import mcws from 'services/mcws/mcws.js'; export default class DictionaryViewTable extends TelemetryTable { constructor(domainObject, openmct, options, metadata = []) { diff --git a/src/dictionaryView/plugin.js b/src/dictionaryView/plugin.js index b2daf05..945108d 100644 --- a/src/dictionaryView/plugin.js +++ b/src/dictionaryView/plugin.js @@ -1,4 +1,4 @@ -import DictionaryViewProvider from './dictionaryViewProvider'; +import DictionaryViewProvider from './dictionaryViewProvider.js'; export default function plugin(options) { return function install(openmct) { diff --git a/src/evrView/EVRTable.js b/src/evrView/EVRTable.js index ca1625a..73850f0 100644 --- a/src/evrView/EVRTable.js +++ b/src/evrView/EVRTable.js @@ -1,5 +1,5 @@ import TelemetryTable from 'openmct.tables.TelemetryTable'; -import EVRLevelIndicatorTableRow from './EVRLevelIndicatorTableRow'; +import EVRLevelIndicatorTableRow from './EVRLevelIndicatorTableRow.js'; export default class EVRTable extends TelemetryTable { initialize() { diff --git a/src/evrView/EVRViewLevelsConfigurationViewProvider.js b/src/evrView/EVRViewLevelsConfigurationViewProvider.js index cfe9210..3674036 100644 --- a/src/evrView/EVRViewLevelsConfigurationViewProvider.js +++ b/src/evrView/EVRViewLevelsConfigurationViewProvider.js @@ -1,5 +1,5 @@ import EVRViewLevelsConfigurationView from './EVRViewLevelsConfigurationView.vue'; -import mount from 'ommUtils/mountVueComponent'; +import mount from 'ommUtils/mountVueComponent.js'; export default function EVRViewLevelsConfigurationViewProvider(openmct, options) { return { diff --git a/src/evrView/EVRViewProvider.js b/src/evrView/EVRViewProvider.js index c2c9374..379b247 100644 --- a/src/evrView/EVRViewProvider.js +++ b/src/evrView/EVRViewProvider.js @@ -1,6 +1,6 @@ -import EVRTable from './EVRTable'; +import EVRTable from './EVRTable.js'; import TableComponent from 'openmct.tables.components.Table'; -import mount from 'ommUtils/mountVueComponent'; +import mount from 'ommUtils/mountVueComponent.js'; const RESTRICTED_VIEWS = ['plot-single', 'table']; const EVR_SOURCES = ['evrHistoricalUrl', 'evrStreamUrl', 'evrLADUrl']; diff --git a/src/evrView/plugin.js b/src/evrView/plugin.js index b6d635c..c0d0652 100644 --- a/src/evrView/plugin.js +++ b/src/evrView/plugin.js @@ -1,6 +1,6 @@ -import EVRViewProvider from './EVRViewProvider'; -import EVRViewLevelsConfigurationViewProvider from './EVRViewLevelsConfigurationViewProvider'; -import VistaTableConfigurationProvider from '../tables/VistaTableConfigurationProvider'; +import EVRViewProvider from './EVRViewProvider.js'; +import EVRViewLevelsConfigurationViewProvider from './EVRViewLevelsConfigurationViewProvider.js'; +import VistaTableConfigurationProvider from '../tables/VistaTableConfigurationProvider.js'; export default function EVRViewPlugin(options) { const { taxonomy, tablePerformanceOptions } = options; diff --git a/src/exportDataAction/ExportDataAction.js b/src/exportDataAction/ExportDataAction.js index 976ab52..e25c3e8 100644 --- a/src/exportDataAction/ExportDataAction.js +++ b/src/exportDataAction/ExportDataAction.js @@ -1,6 +1,6 @@ -import ExportDataTask from './ExportDataTask'; -import SessionService from 'services/session/SessionService'; -import { formatNumberSequence } from 'ommUtils/strings'; +import ExportDataTask from './ExportDataTask.js'; +import SessionService from 'services/session/SessionService.js'; +import { formatNumberSequence } from 'ommUtils/strings.js'; /** * Implements the "Export Data" action, allowing data for Channels, EVRs, diff --git a/src/exportDataAction/plugin.js b/src/exportDataAction/plugin.js index 32777a4..9688f83 100644 --- a/src/exportDataAction/plugin.js +++ b/src/exportDataAction/plugin.js @@ -1,4 +1,4 @@ -import ExportDataAction from './ExportDataAction'; +import ExportDataAction from './ExportDataAction.js'; export default function (validTypes) { return function (openmct) { diff --git a/src/formats/JSONStringFormat.js b/src/formats/JSONStringFormat.js index 2705a24..9b46010 100644 --- a/src/formats/JSONStringFormat.js +++ b/src/formats/JSONStringFormat.js @@ -1,27 +1,26 @@ -define(['lodash'], function (_) { - /** - * Format embedded JavaScript objects as JSON strings for debugging - * - * @implements {Format} - * @constructor - */ - function JSONStringFormat() { +/** + * Format embedded JavaScript objects as JSON strings for debugging + * + * @implements {Format} + */ +class JSONStringFormat { + constructor() { this.key = 'jsonString'; } - JSONStringFormat.prototype.format = function (value) { + format(value) { return JSON.stringify(value); - }; + } - JSONStringFormat.prototype.parse = function (stringValue) { + parse(stringValue) { if (typeof stringValue === 'string') { return JSON.parse(stringValue); } else { return stringValue; } - }; + } - JSONStringFormat.prototype.validate = function (stringValue) { + validate(stringValue) { try { JSON.parse(stringValue); return true; @@ -29,7 +28,7 @@ define(['lodash'], function (_) { console.error('Failed to parse %s', stringValue, error); return false; } - }; + } +} - return JSONStringFormat; -}); +export default JSONStringFormat; diff --git a/src/formats/LMSTFormat.js b/src/formats/LMSTFormat.js index c51afb8..26851e9 100644 --- a/src/formats/LMSTFormat.js +++ b/src/formats/LMSTFormat.js @@ -1,61 +1,58 @@ -define(['moment'], function (moment) { - var MSL_EPOCH = moment.utc(Date.UTC(2012, 7, 5, 13, 49, 59)), - MARS_SECONDS_PER_EARTH_SECOND = 1.02749125; +import moment from 'moment'; - /** - * The LMSTDate formatter takes UTC dates and converts them to the correct - * martian sol. - * - * Martian sols are longer than earth days, so to simplify this and not - * break every currently existing time library and package, we consider a - * martian sol to be 24 hours, but the seconds are longer. - * - * Additionally, the martian epoch is defined differently for each mission. - * This formatter defines the martian epoch according to that set by the - * MSL team for Curiosity. - * - * Thus, it is assumed the numerical form of a SOL is a UTC date time, - * and the string form of a SOL is a string specific to a SOL. Any - * intermediate forms should not be trusted. - * - * The basic translation path for UTC -> SOL is: - * 1. Calculate earth seconds elapsed since SOL0 - * 2. Convert earth seconds elapsed to mars seconds elapsed - * 3. Convert mars seconds elapsed to SOL text format. - * - * Converting from a SOL -> UTC is done as follows: - * 1. Parse special SOL format string to determine mars seconds elapsed - * 2. Convert mars seconds elapsed to earth seconds elapsed. - * 3. Calculate UTC value by adding SOL0 to earth seconds elapsed. - * - * @implements {Format} - * @constructor - */ - function LMST(epoch) { - this.key = 'lmst'; - this.epoch = moment.utc(epoch) || MSL_EPOCH; +const MSL_EPOCH = moment.utc(Date.UTC(2012, 7, 5, 13, 49, 59)); +const MARS_SECONDS_PER_EARTH_SECOND = 1.02749125; - this.format = this.format.bind(this); - this.parse = this.parse.bind(this); - } - - LMST.prototype.FORMAT = '[SOL]-DDD[M]HH:mm:ss.SSS'; - LMST.prototype.TIME_FORMAT = '[M]HH:mm:ss.SSS'; - LMST.prototype.TIME_FORMATS = [ - LMST.prototype.TIME_FORMAT, +/** + * The LMSTDate formatter takes UTC dates and converts them to the correct + * martian sol. + * + * Martian sols are longer than earth days, so to simplify this and not + * break every currently existing time library and package, we consider a + * martian sol to be 24 hours, but the seconds are longer. + * + * Additionally, the martian epoch is defined differently for each mission. + * This formatter defines the martian epoch according to that set by the + * MSL team for Curiosity. + * + * Thus, it is assumed the numerical form of a SOL is a UTC date time, + * and the string form of a SOL is a string specific to a SOL. Any + * intermediate forms should not be trusted. + * + * The basic translation path for UTC -> SOL is: + * 1. Calculate earth seconds elapsed since SOL0 + * 2. Convert earth seconds elapsed to mars seconds elapsed + * 3. Convert mars seconds elapsed to SOL text format. + * + * Converting from a SOL -> UTC is done as follows: + * 1. Parse special SOL format string to determine mars seconds elapsed + * 2. Convert mars seconds elapsed to earth seconds elapsed. + * 3. Calculate UTC value by adding SOL0 to earth seconds elapsed. + * + * @implements {Format} + */ +class LMST { + static FORMAT = '[SOL]-DDD[M]HH:mm:ss.SSS'; + static TIME_FORMAT = '[M]HH:mm:ss.SSS'; + static TIME_FORMATS = [ + '[M]HH:mm:ss.SSS', '[M]HH:mm:ss.SSS', '[M]HH:mm:ss', '[M]HH:mm', '[M]HH' ]; + static PATTERN = /SOL-(\d+)([M]\d{2}:\d{2}:\d{2}\.\d{0,4})?/; - LMST.prototype.PATTERN = /SOL-(\d+)([M]\d{2}:\d{2}:\d{2}\.\d{0,4})?/; + constructor(epoch) { + this.key = 'lmst'; + this.epoch = moment.utc(epoch) || MSL_EPOCH; + } /** * @param {Number} utcValue a numerical representation of a utc date. * @returns {String} the utc date as a string representing MSL-SOL time. */ - LMST.prototype.format = function (utcValue) { + format(utcValue) { if (!utcValue) { return ''; } @@ -64,50 +61,50 @@ define(['moment'], function (moment) { return utcValue; } - var earthTimeElapsed = moment.utc(utcValue) - this.epoch, - marsTimeElapsed = earthTimeElapsed / MARS_SECONDS_PER_EARTH_SECOND, - solDecimal = marsTimeElapsed / moment.utc(0).add(1, 'day'), - sol = Math.floor(solDecimal), - timeDecimal = solDecimal - sol, - time = moment.utc(timeDecimal * moment.utc(0).add(1, 'day')); + const earthTimeElapsed = moment.utc(utcValue) - this.epoch; + const marsTimeElapsed = earthTimeElapsed / MARS_SECONDS_PER_EARTH_SECOND; + const solDecimal = marsTimeElapsed / moment.utc(0).add(1, 'day'); + const sol = Math.floor(solDecimal); + const timeDecimal = solDecimal - sol; + const time = moment.utc(timeDecimal * moment.utc(0).add(1, 'day')); - sol = String(sol); - while (sol.length < 4) { - sol = '0' + sol; + let solString = String(sol); + while (solString.length < 4) { + solString = '0' + solString; } - return 'SOL-' + sol + time.format(this.TIME_FORMAT); - }; + return 'SOL-' + solString + time.format(LMST.TIME_FORMAT); + } /** * * @param {String} solDate a string sol date e.g. SOL-0000T12:00:00. * @returns {Number} the utc datetime equivalent of the sol. */ - LMST.prototype.parse = function (text) { + parse(text) { if (!this.validate(text)) { return undefined; } - var matches = this.PATTERN.exec(text), - sol = matches[1], - time = matches[2], - solValue = moment.utc(0).add(sol, 'days'), - timeValue = time ? moment.utc(time, this.TIME_FORMATS) : moment.utc(0), - marsTimeElapsed = solValue.add({ - hours: timeValue.hours(), - minutes: timeValue.minutes(), - seconds: timeValue.seconds(), - milliseconds: timeValue.milliseconds() - }), - earthTimeElapsed = marsTimeElapsed * MARS_SECONDS_PER_EARTH_SECOND, - value = this.epoch + Math.round(earthTimeElapsed); + const matches = LMST.PATTERN.exec(text); + const sol = matches[1]; + const time = matches[2]; + const solValue = moment.utc(0).add(sol, 'days'); + const timeValue = time ? moment.utc(time, LMST.TIME_FORMATS) : moment.utc(0); + const marsTimeElapsed = solValue.add({ + hours: timeValue.hours(), + minutes: timeValue.minutes(), + seconds: timeValue.seconds(), + milliseconds: timeValue.milliseconds() + }); + const earthTimeElapsed = marsTimeElapsed * MARS_SECONDS_PER_EARTH_SECOND; + const value = this.epoch + Math.round(earthTimeElapsed); return value; - }; + } - LMST.prototype.validate = function (text) { - return this.PATTERN.test(text); - }; + validate(text) { + return LMST.PATTERN.test(text); + } +} - return LMST; -}); +export default LMST; diff --git a/src/formats/MSLSOLFormat.js b/src/formats/MSLSOLFormat.js index 7e0bcd5..147e50c 100644 --- a/src/formats/MSLSOLFormat.js +++ b/src/formats/MSLSOLFormat.js @@ -1,6 +1,7 @@ -define(['moment'], function (moment) { - var MSL_EPOCH = moment.utc(Date.UTC(2012, 7, 5, 13, 49, 59)), - MARS_SECONDS_PER_EARTH_SECOND = 1.02749125; +import moment from 'moment'; + +const MSL_EPOCH = moment.utc(Date.UTC(2012, 7, 5, 13, 49, 59)); +const MARS_SECONDS_PER_EARTH_SECOND = 1.02749125; /** * The MSLSolDate formatter takes UTC dates and converts them to the correct @@ -29,29 +30,28 @@ define(['moment'], function (moment) { * 3. Calculate UTC value by adding SOL0 to earth seconds elapsed. * * @implements {Format} - * @constructor */ - function MSLSOLFormat() { - this.key = 'msl.sol'; - } - - MSLSOLFormat.prototype.FORMAT = '[SOL]-DDD[M]HH:mm:ss.SSS'; - MSLSOLFormat.prototype.TIME_FORMAT = '[M]HH:mm:ss.SSS'; - MSLSOLFormat.prototype.TIME_FORMATS = [ - MSLSOLFormat.prototype.TIME_FORMAT, +class MSLSOLFormat { + static FORMAT = '[SOL]-DDD[M]HH:mm:ss.SSS'; + static TIME_FORMAT = '[M]HH:mm:ss.SSS'; + static TIME_FORMATS = [ + '[M]HH:mm:ss.SSS', '[M]HH:mm:ss.SSS', '[M]HH:mm:ss', '[M]HH:mm', '[M]HH' ]; + static PATTERN = /SOL-(\d+)([M]\d{2}:\d{2}:\d{2}\.\d{0,4})?/; - MSLSOLFormat.prototype.PATTERN = /SOL-(\d+)([M]\d{2}:\d{2}:\d{2}\.\d{0,4})?/; + constructor() { + this.key = 'msl.sol'; + } /** * @param {Number} utcValue a numerical representation of a utc date. * @returns {String} the utc date as a string representing MSL-SOL time. */ - MSLSOLFormat.prototype.format = function (utcValue) { + format(utcValue) { if (!utcValue) { return ''; } @@ -60,50 +60,50 @@ define(['moment'], function (moment) { return utcValue; } - var earthTimeElapsed = moment.utc(utcValue) - MSL_EPOCH, - marsTimeElapsed = earthTimeElapsed / MARS_SECONDS_PER_EARTH_SECOND, - solDecimal = marsTimeElapsed / moment.utc(0).add(1, 'day'), - sol = Math.floor(solDecimal), - timeDecimal = solDecimal - sol, - time = moment.utc(timeDecimal * moment.utc(0).add(1, 'day')); + const earthTimeElapsed = moment.utc(utcValue) - MSL_EPOCH; + const marsTimeElapsed = earthTimeElapsed / MARS_SECONDS_PER_EARTH_SECOND; + const solDecimal = marsTimeElapsed / moment.utc(0).add(1, 'day'); + const sol = Math.floor(solDecimal); + const timeDecimal = solDecimal - sol; + const time = moment.utc(timeDecimal * moment.utc(0).add(1, 'day')); - sol = String(sol); - while (sol.length < 4) { - sol = '0' + sol; + let solString = String(sol); + while (solString.length < 4) { + solString = '0' + solString; } - return 'SOL-' + sol + time.format(this.TIME_FORMAT); - }; + return 'SOL-' + solString + time.format(MSLSOLFormat.TIME_FORMAT); + } /** * * @param {String} solDate a string sol date e.g. SOL-0000T12:00:00. * @returns {Number} the utc datetime equivalent of the sol. */ - MSLSOLFormat.prototype.parse = function (text) { + parse(text) { if (!this.validate(text)) { return undefined; } - var matches = this.PATTERN.exec(text), - sol = matches[1], - time = matches[2], - solValue = moment.utc(0).add(sol, 'days'), - timeValue = time ? moment.utc(time, this.TIME_FORMATS) : moment.utc(0), - marsTimeElapsed = solValue.add({ + const matches = MSLSOLFormat.PATTERN.exec(text); + const sol = matches[1]; + const time = matches[2]; + const solValue = moment.utc(0).add(sol, 'days'); + const timeValue = time ? moment.utc(time, MSLSOLFormat.TIME_FORMATS) : moment.utc(0); + const marsTimeElapsed = solValue.add({ hours: timeValue.hours(), minutes: timeValue.minutes(), seconds: timeValue.seconds(), milliseconds: timeValue.milliseconds() - }), - earthTimeElapsed = marsTimeElapsed * MARS_SECONDS_PER_EARTH_SECOND, - value = MSL_EPOCH + Math.round(earthTimeElapsed); + }); + const earthTimeElapsed = marsTimeElapsed * MARS_SECONDS_PER_EARTH_SECOND; + const value = MSL_EPOCH + Math.round(earthTimeElapsed); return value; - }; + } - MSLSOLFormat.prototype.validate = function (text) { - return this.PATTERN.test(text); - }; + validate(text) { + return MSLSOLFormat.PATTERN.test(text); + } +} - return MSLSOLFormat; -}); +export default MSLSOLFormat; diff --git a/src/formats/SCLKFloat64Format.js b/src/formats/SCLKFloat64Format.js index a76d9d8..f8ccc69 100644 --- a/src/formats/SCLKFloat64Format.js +++ b/src/formats/SCLKFloat64Format.js @@ -1,26 +1,26 @@ -define(['lodash'], function (_) { - /** - * Format for SCLK values as 64 bit float. - * - * @implements {Format} - * @constructor - */ - function SCLKFloat64Format() { +/** + * Format for SCLK values as 64 bit float. + * + * @implements {Format} + */ +class SCLKFloat64Format { + constructor() { this.key = 'sclk.float64'; } - SCLKFloat64Format.prototype.format = function (value) { + format(value) { return value && value.toString ? value.toString() : ''; - }; + } - SCLKFloat64Format.prototype.parse = function (stringValue) { + parse(stringValue) { return parseFloat(stringValue); - }; + } - SCLKFloat64Format.prototype.validate = function (stringValue) { - var floatValue = this.parse(stringValue); - return !_.isNaN(floatValue); - }; + validate(stringValue) { + const floatValue = this.parse(stringValue); + + return !Number.isNaN(floatValue); + } +} - return SCLKFloat64Format; -}); +export default SCLKFloat64Format; diff --git a/src/formats/UTCDayOfYearFormat.js b/src/formats/UTCDayOfYearFormat.js index 2ed1c32..f884a9c 100644 --- a/src/formats/UTCDayOfYearFormat.js +++ b/src/formats/UTCDayOfYearFormat.js @@ -1,5 +1,5 @@ import moment from 'moment'; -import { DOY_PATTERN, inlineParseDOYString } from './utils/DOY'; +import { DOY_PATTERN, inlineParseDOYString } from './utils/DOY.js'; /** * Returns an appropriate time format based on the provided value and diff --git a/src/formats/UTCFormat.js b/src/formats/UTCFormat.js index e27bc21..932b17f 100644 --- a/src/formats/UTCFormat.js +++ b/src/formats/UTCFormat.js @@ -1,5 +1,5 @@ import moment from 'moment'; -import { DOY_PATTERN, inlineParseDOYString } from './utils/DOY'; +import { DOY_PATTERN, inlineParseDOYString } from './utils/DOY.js'; /** * Returns an appropriate time format based on the provided value and diff --git a/src/formats/plugin.js b/src/formats/plugin.js index 840049a..5dcc063 100644 --- a/src/formats/plugin.js +++ b/src/formats/plugin.js @@ -1,28 +1,19 @@ -define([ - './MSLSOLFormat', - './LMSTFormat', - './SCLKFloat64Format', - './UTCDayOfYearFormat', - './UTCFormat', - './JSONStringFormat' -], function ( - MSLSOLFormat, - LMSTFormat, - SCLKFloat64Format, - UTCDayOfYearFormat, - UTCFormat, - JSONStringFormat -) { - function FormatPlugin(options) { - return function install(openmct) { - openmct.telemetry.addFormat(new UTCDayOfYearFormat.default()); - openmct.telemetry.addFormat(new UTCFormat.default()); - openmct.telemetry.addFormat(new MSLSOLFormat()); - openmct.telemetry.addFormat(new LMSTFormat()); - openmct.telemetry.addFormat(new SCLKFloat64Format()); - openmct.telemetry.addFormat(new JSONStringFormat()); - }; - } +import MSLSOLFormat from './MSLSOLFormat.js'; +import LMSTFormat from './LMSTFormat.js'; +import SCLKFloat64Format from './SCLKFloat64Format.js'; +import UTCDayOfYearFormat from './UTCDayOfYearFormat.js'; +import UTCFormat from './UTCFormat.js'; +import JSONStringFormat from './JSONStringFormat.js'; - return FormatPlugin; -}); +function FormatPlugin(options) { + return function install(openmct) { + openmct.telemetry.addFormat(new UTCDayOfYearFormat()); + openmct.telemetry.addFormat(new UTCFormat()); + openmct.telemetry.addFormat(new MSLSOLFormat()); + openmct.telemetry.addFormat(new LMSTFormat()); + openmct.telemetry.addFormat(new SCLKFloat64Format()); + openmct.telemetry.addFormat(new JSONStringFormat()); + }; +} + +export default FormatPlugin; diff --git a/src/frameEventFilterView/FrameEventFilterViewProvider.js b/src/frameEventFilterView/FrameEventFilterViewProvider.js index 2dc7238..30a840e 100644 --- a/src/frameEventFilterView/FrameEventFilterViewProvider.js +++ b/src/frameEventFilterView/FrameEventFilterViewProvider.js @@ -1,6 +1,6 @@ import FrameEventFilterTable from './FrameEventFilterTable.js'; import TableComponent from 'openmct.tables.components.Table'; -import mount from 'ommUtils/mountVueComponent'; +import mount from 'ommUtils/mountVueComponent.js'; export default class FrameEventFilterViewProvider { constructor(openmct, options) { diff --git a/src/frameaccountability/components/frameAccountability.js b/src/frameaccountability/components/frameAccountability.js index cab1534..b91a056 100644 --- a/src/frameaccountability/components/frameAccountability.js +++ b/src/frameaccountability/components/frameAccountability.js @@ -1,230 +1,222 @@ -define([ - './res/frameAccountability.html', - './frameAccountabilityNode', - './frameAccountabilityBadFrames.vue', - '../sortedEventsCollection', - 'saveAs' -], function ( - FrameAccountabilityTemplate, - frameAccountabilityNode, - frameAccountabilityBadFrames, - SortedEventsCollection, - saveAsObject -) { - return { - template: FrameAccountabilityTemplate, - inject: ['openmct', 'domainObject', 'table', 'FLAG_COLORS', 'expectedVcidList'], - components: { - frameAccountabilityNode, - frameAccountabilityBadFrames: frameAccountabilityBadFrames.default - }, - data() { - return { - vcids: [], - vcidsWithBadFrames: {}, - events: {}, - subscriptions: {}, - badFrames: [], - sortedEventsCollections: {}, - vcid: undefined, - frameEventValueMetadatas: undefined, - commandEventValueMetadatas: undefined, - unexpectedVcids: {} - }; +import FrameAccountabilityTemplate from './res/frameAccountability.html'; +import frameAccountabilityNode from './frameAccountabilityNode.js'; +import frameAccountabilityBadFrames from './frameAccountabilityBadFrames.vue'; +import SortedEventsCollection from '../sortedEventsCollection.js'; +import { saveAs } from 'file-saver'; + +export default { + template: FrameAccountabilityTemplate, + inject: ['openmct', 'domainObject', 'table', 'FLAG_COLORS', 'expectedVcidList'], + components: { + frameAccountabilityNode, + frameAccountabilityBadFrames: frameAccountabilityBadFrames + }, + data() { + return { + vcids: [], + vcidsWithBadFrames: {}, + events: {}, + subscriptions: {}, + badFrames: [], + sortedEventsCollections: {}, + vcid: undefined, + frameEventValueMetadatas: undefined, + commandEventValueMetadatas: undefined, + unexpectedVcids: {} + }; + }, + methods: { + addEvents(domainObject) { + if (domainObject.type === 'vista.frameEvent') { + this.addFrameEvents(domainObject); + } else if (domainObject.type === 'vista.commandEvents') { + this.addCommandEvents(domainObject); + } }, - methods: { - addEvents(domainObject) { - if (domainObject.type === 'vista.frameEvent') { - this.addFrameEvents(domainObject); - } else if (domainObject.type === 'vista.commandEvents') { - this.addCommandEvents(domainObject); - } - }, - removeEvents(identifier) { - const keyString = this.openmct.objects.makeKeyString(identifier); + removeEvents(identifier) { + const keyString = this.openmct.objects.makeKeyString(identifier); - if (this.subscriptions[keyString] && typeof this.subscriptions[keyString] === 'function') { - this.subscriptions[keyString](); - delete this.subscriptions[keyString]; - } - }, - addCommandEvents(domainObject) { - if (!this.commandEventMetadata || !this.commandEventFormats) { - this.commandEventMetadata = this.openmct.telemetry.getMetadata(domainObject); - this.commandEventFormats = this.openmct.telemetry.getFormatMap(this.commandEventMetadata); - this.commandEventValueMetadatas = this.commandEventMetadata.valueMetadatas; - } - - const unsubscribe = this.openmct.telemetry.subscribe( - domainObject, - this.processRealtimeDatum('commandEvent') - ); - const keyString = this.openmct.objects.makeKeyString(domainObject.identifier); - - this.subscriptions[keyString] = unsubscribe; - }, - addFrameEvents(domainObject) { - if (!this.frameEventMetadata || !this.frameEventFormats) { - this.frameEventMetadata = this.openmct.telemetry.getMetadata(domainObject); - this.frameEventFormats = this.openmct.telemetry.getFormatMap(this.frameEventMetadata); - this.frameEventValueMetadatas = this.frameEventMetadata.valueMetadatas; - - this.table.metadata = this.frameEventValueMetadatas; - this.table.addColumnsForObject(domainObject); - } + if (this.subscriptions[keyString] && typeof this.subscriptions[keyString] === 'function') { + this.subscriptions[keyString](); + delete this.subscriptions[keyString]; + } + }, + addCommandEvents(domainObject) { + if (!this.commandEventMetadata || !this.commandEventFormats) { + this.commandEventMetadata = this.openmct.telemetry.getMetadata(domainObject); + this.commandEventFormats = this.openmct.telemetry.getFormatMap(this.commandEventMetadata); + this.commandEventValueMetadatas = this.commandEventMetadata.valueMetadatas; + } - const unsubscribe = this.openmct.telemetry.subscribe( - domainObject, - this.processRealtimeDatum('frameEvent') - ); - const keyString = this.openmct.objects.makeKeyString(domainObject.identifier); - - this.subscriptions[keyString] = unsubscribe; - }, - processRealtimeDatum(eventType) { - return (datum) => { - let event = this.standardizeEvent(eventType, datum); - - if (eventType === 'frameEvent') { - this.processFrameEvent(event); - } else if (eventType === 'commandEvent') { - this.processCommandEvent(event); - } - }; - }, - standardizeEvent(eventType, datum) { - let event = { type: eventType }; + const unsubscribe = this.openmct.telemetry.subscribe( + domainObject, + this.processRealtimeDatum('commandEvent') + ); + const keyString = this.openmct.objects.makeKeyString(domainObject.identifier); - this[`${eventType}ValueMetadatas`].forEach((valueMetadata) => { - const key = valueMetadata.key; - const source = valueMetadata.source; + this.subscriptions[keyString] = unsubscribe; + }, + addFrameEvents(domainObject) { + if (!this.frameEventMetadata || !this.frameEventFormats) { + this.frameEventMetadata = this.openmct.telemetry.getMetadata(domainObject); + this.frameEventFormats = this.openmct.telemetry.getFormatMap(this.frameEventMetadata); + this.frameEventValueMetadatas = this.frameEventMetadata.valueMetadatas; + + this.table.metadata = this.frameEventValueMetadatas; + this.table.addColumnsForObject(domainObject); + } - event[source] = this[`${eventType}Formats`][key].format(datum[source]); - }); + const unsubscribe = this.openmct.telemetry.subscribe( + domainObject, + this.processRealtimeDatum('frameEvent') + ); + const keyString = this.openmct.objects.makeKeyString(domainObject.identifier); - return event; - }, - processCommandEvent(commandEvent) { - this.processVCID(commandEvent.vcid); - this.addToSortedCollection(commandEvent); - }, - processFrameEvent(frameEvent) { - this.processVCID(frameEvent.vcid); - - if (frameEvent.message_type === 'BadTelemetryFrame') { - this.processBadTelemetryFrame(frameEvent); + this.subscriptions[keyString] = unsubscribe; + }, + processRealtimeDatum(eventType) { + return (datum) => { + let event = this.standardizeEvent(eventType, datum); + + if (eventType === 'frameEvent') { + this.processFrameEvent(event); + } else if (eventType === 'commandEvent') { + this.processCommandEvent(event); } + }; + }, + standardizeEvent(eventType, datum) { + let event = { type: eventType }; - this.addToSortedCollection(frameEvent); - }, - processVCID(vcid) { - if (!this.vcids.includes(vcid)) { - this.vcids.push(vcid); + this[`${eventType}ValueMetadatas`].forEach((valueMetadata) => { + const key = valueMetadata.key; + const source = valueMetadata.source; - if (!this.expectedVcidList.includes(Number(vcid))) { - this.unexpectedVcids[vcid] = true; - } - } - }, - processBadTelemetryFrame(frameEvent) { - if (this.vcid && this.vcid === frameEvent.vcid) { - this.badFrames.push(frameEvent); - } - if (!this.vcidsWithBadFrames[frameEvent.vcid]) { - this.vcidsWithBadFrames[frameEvent.vcid] = true; - } - }, - addToSortedCollection(event) { - const vcid = event.vcid; + event[source] = this[`${eventType}Formats`][key].format(datum[source]); + }); - if (this.sortedEventsCollections[vcid] === undefined) { - this.createEventsCollection(vcid); - } + return event; + }, + processCommandEvent(commandEvent) { + this.processVCID(commandEvent.vcid); + this.addToSortedCollection(commandEvent); + }, + processFrameEvent(frameEvent) { + this.processVCID(frameEvent.vcid); - this.sortedEventsCollections[vcid].addRows([event]); - }, - createEventsCollection(vcid) { - const collection = new SortedEventsCollection.default(); - - collection.on('add', this.updateFramesCollection(vcid)); - this.sortedEventsCollections[vcid] = collection; - }, - updateFramesCollection(vcid) { - return (datum) => { - const collection = this.sortedEventsCollections[vcid]; - - this.events[vcid] = - this.events[vcid] !== undefined ? collection.getRows() : [collection.getRows()]; - }; - }, - lastChild(vcid) { - let children = this.events[vcid]; - - return children[children.length - 1]; - }, - hasBadFrames(vcid) { - return this.vcidsWithBadFrames[vcid]; - }, - showBadFrames(vcid) { - this.vcid = vcid; - this.badFrames = this.events[this.vcid].filter((event) => { - return event.type === 'frameEvent' && event.message_type === 'BadTelemetryFrame'; - }); - }, - hideBadFrames() { - this.vcid = undefined; - this.badFrames = []; - }, - exportAsText() { - let textOutput = ''; - - this.vcids.forEach((vcid) => { - textOutput += 'VC-' + vcid + ' ' + 'Events' + '\n'; - - this.events[vcid].forEach((event, index, array) => { - if (event.type === 'frameEvent') { - textOutput += - '\t' + event.event_time + ': ' + event.message_type + ' : Frame Event' + '\n'; - textOutput += '\t\t' + event.summary + '\n'; - } else if (event.type === 'commandEvent') { - textOutput += - '\t' + event.event_time + ': ' + event.status + ' : Command Event' + '\n'; - textOutput += '\t\t' + event.status + '\n'; - } - - if (index === array.length - 1) { - textOutput += '\n'; - } - }); - }); + if (frameEvent.message_type === 'BadTelemetryFrame') { + this.processBadTelemetryFrame(frameEvent); + } - let blob = new Blob([textOutput], { type: 'text' }), - filename = this.domainObject.name + '- Text Output'; + this.addToSortedCollection(frameEvent); + }, + processVCID(vcid) { + if (!this.vcids.includes(vcid)) { + this.vcids.push(vcid); - saveAsObject.saveAs(blob, filename); - }, - isVcidUnexpected(vcid) { - return true; + if (!this.expectedVcidList.includes(Number(vcid))) { + this.unexpectedVcids[vcid] = true; + } } }, - mounted() { - this.composition = this.openmct.composition.get(this.domainObject); - this.composition.on('add', this.addEvents); - this.composition.on('remove', this.removeEvents); - this.composition.load(); + processBadTelemetryFrame(frameEvent) { + if (this.vcid && this.vcid === frameEvent.vcid) { + this.badFrames.push(frameEvent); + } + if (!this.vcidsWithBadFrames[frameEvent.vcid]) { + this.vcidsWithBadFrames[frameEvent.vcid] = true; + } }, - beforeUnmount() { - this.composition.off('add', this.addEvents); - this.composition.off('remove', this.removeEvents); + addToSortedCollection(event) { + const vcid = event.vcid; - Object.entries(this.sortedEventsCollections).forEach(([vcid, collection]) => { - collection.off('add', this.updateFramesCollection(vcid)); - delete this.sortedEventsCollections[vcid]; + if (this.sortedEventsCollections[vcid] === undefined) { + this.createEventsCollection(vcid); + } + + this.sortedEventsCollections[vcid].addRows([event]); + }, + createEventsCollection(vcid) { + const collection = new SortedEventsCollection(); + + collection.on('add', this.updateFramesCollection(vcid)); + this.sortedEventsCollections[vcid] = collection; + }, + updateFramesCollection(vcid) { + return (datum) => { + const collection = this.sortedEventsCollections[vcid]; + + this.events[vcid] = + this.events[vcid] !== undefined ? collection.getRows() : [collection.getRows()]; + }; + }, + lastChild(vcid) { + let children = this.events[vcid]; + + return children[children.length - 1]; + }, + hasBadFrames(vcid) { + return this.vcidsWithBadFrames[vcid]; + }, + showBadFrames(vcid) { + this.vcid = vcid; + this.badFrames = this.events[this.vcid].filter((event) => { + return event.type === 'frameEvent' && event.message_type === 'BadTelemetryFrame'; }); + }, + hideBadFrames() { + this.vcid = undefined; + this.badFrames = []; + }, + exportAsText() { + let textOutput = ''; + + this.vcids.forEach((vcid) => { + textOutput += 'VC-' + vcid + ' ' + 'Events' + '\n'; + + this.events[vcid].forEach((event, index, array) => { + if (event.type === 'frameEvent') { + textOutput += + '\t' + event.event_time + ': ' + event.message_type + ' : Frame Event' + '\n'; + textOutput += '\t\t' + event.summary + '\n'; + } else if (event.type === 'commandEvent') { + textOutput += + '\t' + event.event_time + ': ' + event.status + ' : Command Event' + '\n'; + textOutput += '\t\t' + event.status + '\n'; + } - Object.values(this.subscriptions).forEach((unsubscribe) => { - unsubscribe(); + if (index === array.length - 1) { + textOutput += '\n'; + } + }); }); + + let blob = new Blob([textOutput], { type: 'text' }), + filename = this.domainObject.name + '- Text Output'; + + saveAs(blob, filename); + }, + isVcidUnexpected(vcid) { + return true; } - }; -}); + }, + mounted() { + this.composition = this.openmct.composition.get(this.domainObject); + this.composition.on('add', this.addEvents); + this.composition.on('remove', this.removeEvents); + this.composition.load(); + }, + beforeUnmount() { + this.composition.off('add', this.addEvents); + this.composition.off('remove', this.removeEvents); + + Object.entries(this.sortedEventsCollections).forEach(([vcid, collection]) => { + collection.off('add', this.updateFramesCollection(vcid)); + delete this.sortedEventsCollections[vcid]; + }); + + Object.values(this.subscriptions).forEach((unsubscribe) => { + unsubscribe(); + }); + } +}; diff --git a/src/frameaccountability/components/frameAccountabilityNode.js b/src/frameaccountability/components/frameAccountabilityNode.js index 2723847..c077e5d 100644 --- a/src/frameaccountability/components/frameAccountabilityNode.js +++ b/src/frameaccountability/components/frameAccountabilityNode.js @@ -1,57 +1,57 @@ -define(['./res/frameAccountabilityNode.html'], function (vcidTemplate) { - const PAGE_THRESHOLD = 50; +import vcidTemplate from './res/frameAccountabilityNode.html'; - return { - name: 'frame-accountability-node', - template: vcidTemplate, - inject: ['FLAG_COLORS'], - props: [ - 'title', - 'iconClass', - 'flagColor', - 'children', - 'finalChild', - 'vcid', - 'showBadFramesLink', - 'unknownVCID' - ], - data() { - return { - expanded: false, - lazyLoadingIndex: 1 - }; +const PAGE_THRESHOLD = 50; + +export default { + name: 'frame-accountability-node', + template: vcidTemplate, + inject: ['FLAG_COLORS'], + props: [ + 'title', + 'iconClass', + 'flagColor', + 'children', + 'finalChild', + 'vcid', + 'showBadFramesLink', + 'unknownVCID' + ], + data() { + return { + expanded: false, + lazyLoadingIndex: 1 + }; + }, + computed: { + maxChildren() { + return PAGE_THRESHOLD * this.lazyLoadingIndex; }, - computed: { - maxChildren() { - return PAGE_THRESHOLD * this.lazyLoadingIndex; - }, - lazyLoadedChildren() { - return this.children.slice(0, this.maxChildren); - }, - numHiddenChildren() { - if (this.children.length > this.maxChildren) { - return this.children.length - this.maxChildren; - } else { - return 0; - } - } + lazyLoadedChildren() { + return this.children.slice(0, this.maxChildren); }, - methods: { - toggleExpand() { - if (this.expanded) { - this.lazyLoadingIndex = 1; - } + numHiddenChildren() { + if (this.children.length > this.maxChildren) { + return this.children.length - this.maxChildren; + } else { + return 0; + } + } + }, + methods: { + toggleExpand() { + if (this.expanded) { + this.lazyLoadingIndex = 1; + } - this.expanded = !this.expanded; - }, - loadMore() { - if (this.numHiddenChildren > 0) { - this.lazyLoadingIndex++; - } - }, - emitShowBadFrames() { - this.$emit('show-bad-frames', this.vcid); + this.expanded = !this.expanded; + }, + loadMore() { + if (this.numHiddenChildren > 0) { + this.lazyLoadingIndex++; } + }, + emitShowBadFrames() { + this.$emit('show-bad-frames', this.vcid); } - }; -}); + } +}; diff --git a/src/frameaccountability/frameAccountabilityViewProvider.js b/src/frameaccountability/frameAccountabilityViewProvider.js index dd5014a..70802b6 100644 --- a/src/frameaccountability/frameAccountabilityViewProvider.js +++ b/src/frameaccountability/frameAccountabilityViewProvider.js @@ -1,6 +1,6 @@ -import frameAccountability from './components/frameAccountability'; -import BadFramesTelemetryTable from './BadFramesTelemetryTable'; -import mount from 'ommUtils/mountVueComponent'; +import frameAccountability from './components/frameAccountability.js'; +import BadFramesTelemetryTable from './BadFramesTelemetryTable.js'; +import mount from 'ommUtils/mountVueComponent.js'; const FLAG_COLORS = { InSync: '#7FFF00', diff --git a/src/frameaccountability/plugin.js b/src/frameaccountability/plugin.js index 72fa7ed..289e5f0 100644 --- a/src/frameaccountability/plugin.js +++ b/src/frameaccountability/plugin.js @@ -1,5 +1,5 @@ -import FrameAccountabilityViewProvider from './frameAccountabilityViewProvider'; -import FrameAccountabilityCompositionPolicy from './frameAccountabilityCompositionPolicy'; +import FrameAccountabilityViewProvider from './frameAccountabilityViewProvider.js'; +import FrameAccountabilityCompositionPolicy from './frameAccountabilityCompositionPolicy.js'; export default function install(options) { return function FrameAccountabilityPlugin(openmct) { diff --git a/src/framesummary/FrameWatchColumn.js b/src/framesummary/FrameWatchColumn.js index 3c574a6..4f73577 100644 --- a/src/framesummary/FrameWatchColumn.js +++ b/src/framesummary/FrameWatchColumn.js @@ -1,38 +1,34 @@ -define(function () { - class FrameWatchColumn { - constructor(key, title) { - this.key = key; - this.title = title; - } - - getKey() { - return this.key; - } +export default class FrameWatchColumn { + constructor(key, title) { + this.key = key; + this.title = title; + } - getTitle() { - return this.title; - } + getKey() { + return this.key; + } - getMetadatum() { - return undefined; - } + getTitle() { + return this.title; + } - hasValueForDatum(telemetryDatum) { - return Object.hasOwn(telemetryDatum, this.key); - } + getMetadatum() { + return undefined; + } - getRawValue(telemetryDatum) { - return telemetryDatum[this.key]; - } + hasValueForDatum(telemetryDatum) { + return Object.hasOwn(telemetryDatum, this.key); + } - getFormattedValue(telemetryValue) { - return telemetryValue; - } + getRawValue(telemetryDatum) { + return telemetryDatum[this.key]; + } - getParsedValue(telemetryValue) { - return telemetryValue; - } + getFormattedValue(telemetryValue) { + return telemetryValue; } - return FrameWatchColumn; -}); + getParsedValue(telemetryValue) { + return telemetryValue; + } +} diff --git a/src/framesummary/FrameWatchConfigurationViewProvider.js b/src/framesummary/FrameWatchConfigurationViewProvider.js index 72b6466..e793560 100644 --- a/src/framesummary/FrameWatchConfigurationViewProvider.js +++ b/src/framesummary/FrameWatchConfigurationViewProvider.js @@ -1,5 +1,5 @@ -import mount from 'ommUtils/mountVueComponent'; -import FrameWatchTableConfiguration from './FrameWatchTableConfiguration'; +import mount from 'ommUtils/mountVueComponent.js'; +import FrameWatchTableConfiguration from './FrameWatchTableConfiguration.js'; import TableConfigurationComponent from 'openmct.tables.components.TableConfiguration'; export default class FrameWatchConfigurationViewProvider { diff --git a/src/framesummary/FrameWatchTableConfiguration.js b/src/framesummary/FrameWatchTableConfiguration.js index 8924579..7c34ba4 100644 --- a/src/framesummary/FrameWatchTableConfiguration.js +++ b/src/framesummary/FrameWatchTableConfiguration.js @@ -1,6 +1,6 @@ import TelemetryTableConfiguration from 'openmct.tables.TelemetryTableConfiguration'; -import { config } from './config'; -import FrameWatchColumn from './FrameWatchColumn'; +import { config } from './config.js'; +import FrameWatchColumn from './FrameWatchColumn.js'; export default class FrameWatchTableConfiguration extends TelemetryTableConfiguration { constructor(domainObject, openmct, options, type) { diff --git a/src/framesummary/FrameWatchViewProvider.js b/src/framesummary/FrameWatchViewProvider.js index aec5f9b..d18646c 100644 --- a/src/framesummary/FrameWatchViewProvider.js +++ b/src/framesummary/FrameWatchViewProvider.js @@ -1,7 +1,7 @@ -import mount from 'ommUtils/mountVueComponent'; -import FrameWatchTable from './FrameWatchTable'; +import mount from 'ommUtils/mountVueComponent.js'; +import FrameWatchTable from './FrameWatchTable.js'; import FrameWatchViewComponent from './components/FrameWatchViewComponent.vue'; -import { FRAME_WATCH_TYPE } from './config'; +import { FRAME_WATCH_TYPE } from './config.js'; export default class FrameWatchViewProvider { constructor(openmct, key, name, options, type = FRAME_WATCH_TYPE) { diff --git a/src/framesummary/config.js b/src/framesummary/config.js index d42cdd4..50264b5 100644 --- a/src/framesummary/config.js +++ b/src/framesummary/config.js @@ -1,4 +1,4 @@ -import padStart from 'lodash/padStart'; +import padStart from 'lodash'; export const ENCODING_WATCH_TYPE = 'vista.encodingWatch'; export const FRAME_WATCH_TYPE = 'vista.frameWatch'; diff --git a/src/framesummary/encodingwatch/EncodingWatchRow.js b/src/framesummary/encodingwatch/EncodingWatchRow.js index 0ef5585..6f730ee 100644 --- a/src/framesummary/encodingwatch/EncodingWatchRow.js +++ b/src/framesummary/encodingwatch/EncodingWatchRow.js @@ -1,6 +1,6 @@ -import FrameWatchRow from '../FrameWatchRow'; -import DatasetCache from 'services/dataset/DatasetCache'; -import Types from '../../types/types'; +import FrameWatchRow from '../FrameWatchRow.js'; +import DatasetCache from 'services/dataset/DatasetCache.js'; +import Types from '../../types/types.js'; export default class EncodingWatchRow extends FrameWatchRow { constructor(datum, columns, objectKeyString, limitEvaluator, rowId, frameEventType) { diff --git a/src/framesummary/plugin.js b/src/framesummary/plugin.js index df51ab2..1dd8f4b 100644 --- a/src/framesummary/plugin.js +++ b/src/framesummary/plugin.js @@ -1,6 +1,6 @@ -import FrameWatchViewProvider from './FrameWatchViewProvider'; -import FrameWatchConfigurationViewProvider from './FrameWatchConfigurationViewProvider'; -import { ENCODING_WATCH_TYPE, FRAME_WATCH_TYPE } from './config'; +import FrameWatchViewProvider from './FrameWatchViewProvider.js'; +import FrameWatchConfigurationViewProvider from './FrameWatchConfigurationViewProvider.js'; +import { ENCODING_WATCH_TYPE, FRAME_WATCH_TYPE } from './config.js'; export default function FrameWatchViewPlugin(options) { return function install(openmct) { diff --git a/src/globalFilters/plugin.js b/src/globalFilters/plugin.js index de7f69d..dab3a62 100644 --- a/src/globalFilters/plugin.js +++ b/src/globalFilters/plugin.js @@ -1,4 +1,4 @@ -import mount from 'ommUtils/mountVueComponent'; +import mount from 'ommUtils/mountVueComponent.js'; import GlobalFilterIndicator from './GlobalFilterIndicator.vue'; export default function plugin(config) { diff --git a/src/historical/HistoricalProvider.js b/src/historical/HistoricalProvider.js index dde2ee7..c524cb9 100644 --- a/src/historical/HistoricalProvider.js +++ b/src/historical/HistoricalProvider.js @@ -1,433 +1,470 @@ -define([ - 'services/mcws/mcws', - 'services/session/SessionService', - 'services/filtering/FilterService', - '../types/types', - '../formats/UTCDayOfYearFormat', - 'lodash', - 'moment' -], function ( - mcwsDefault, - sessionServiceDefault, - filterServiceDefault, - types, - UTCDayOfYearFormat, - _, - moment -) { - const mcws = mcwsDefault.default; - const UTC_FORMAT_KEY = window.openmctMCWSConfig?.time?.utcFormat; - const PROVIDERS = [ - { - // CHANNEL LAD PROVIDER - supportsRequest: function (domainObject, request) { - return ( - domainObject.type === types.Channel.key && - domainObject.telemetry && - (domainObject.telemetry.channelLADUrl || domainObject.telemetry.channelHistoricalUrl) && - (request.strategy === 'latest' || request.size === 1) - ); - }, - batchId: function (domainObject, options) { - return [domainObject.telemetry.channelLADUrl, options.domain, options.filters]; - }, - batchRequest: function (batch) { - const requests = _.values(batch.requestsById); - const params = requests[0].params; - const options = requests[0].options; - - params.lad_type = params.sort; - params.select = - '(dn,eu,channel_id,ert,scet,sclk,lst,record_type,dn_alarm_state,eu_alarm_state,module,realtime,dss_id)'; - params.filter.channel_id__in = _.map(requests, 'domainObject.telemetry.channel_id'); - setSortFilter(params); - - const ladURL = requests[0].domainObject.telemetry.channelLADUrl; - const fallbackHistoricalURL = requests[0].domainObject.telemetry.channelHistoricalUrl; - let requestURL; - - if (!ladURL) { - requestURL = fallbackHistoricalURL; - delete params.lad_type; - } else { - requestURL = ladURL; - } +import mcws from 'services/mcws/mcws.js'; +import sessionService from 'services/session/SessionService.js'; +import filterService from 'services/filtering/FilterService.js'; +import types from '../types/types.js'; +import UTCDayOfYearFormat from '../formats/UTCDayOfYearFormat.js'; +import moment from 'moment'; + +const UTC_FORMAT_KEY = window.openmctMCWSConfig?.time?.utcFormat; + +// Helper function to replace lodash groupBy +function groupBy(array, key) { + return array.reduce((result, item) => { + const group = item[key]; + if (!result[group]) { + result[group] = []; + } + result[group].push(item); + return result; + }, {}); +} + +// Helper function to replace lodash keyBy +function keyBy(array, key) { + return array.reduce((result, item) => { + const groupKey = typeof key === 'function' ? key(item) : item[key]; + result[groupKey] = item; + return result; + }, {}); +} + +// Helper function to replace lodash debounce +function debounce(func, wait = 0) { + let timeout; + return function executedFunction(...args) { + const later = () => { + clearTimeout(timeout); + func(...args); + }; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + }; +} - mcws - .dataTable(requestURL, { signal: options.signal }) - .read(params) - .then(function (res) { - const valuesByChannelId = _.groupBy(res, 'channel_id'); - const toFulfill = _.keyBy(requests, 'domainObject.telemetry.channel_id'); +// CHANNEL LAD PROVIDER +const channelLADProvider = { + supportsRequest: function (domainObject, request) { + return ( + domainObject.type === types.Channel.key && + domainObject.telemetry && + (domainObject.telemetry.channelLADUrl || domainObject.telemetry.channelHistoricalUrl) && + (request.strategy === 'latest' || request.size === 1) + ); + }, + batchId: function (domainObject, options) { + return [domainObject.telemetry.channelLADUrl, options.domain, options.filters]; + }, + batchRequest: function (batch) { + const requests = Object.values(batch.requestsById); + const params = requests[0].params; + const options = requests[0].options; + + params.lad_type = params.sort; + params.select = + '(dn,eu,channel_id,ert,scet,sclk,lst,record_type,dn_alarm_state,eu_alarm_state,module,realtime,dss_id)'; + params.filter.channel_id__in = requests.map((req) => req.domainObject.telemetry.channel_id); + setSortFilter(params); + + const ladURL = requests[0].domainObject.telemetry.channelLADUrl; + const fallbackHistoricalURL = requests[0].domainObject.telemetry.channelHistoricalUrl; + let requestURL; + + if (!ladURL) { + requestURL = fallbackHistoricalURL; + delete params.lad_type; + } else { + requestURL = ladURL; + } - _.forEach(valuesByChannelId, function (values, id) { - toFulfill[id].resolve(values); - delete toFulfill[id]; - }); - _.forEach(toFulfill, function (request) { - request.resolve([]); - }); - }) - .catch((reason) => { - requests.forEach((request) => request.reject(reason)); - }); + mcws + .dataTable(requestURL, { signal: options.signal }) + .read(params) + .then((res) => { + const valuesByChannelId = groupBy(res, 'channel_id'); + const toFulfill = keyBy(requests, (req) => req.domainObject.telemetry.channel_id); + + Object.entries(valuesByChannelId).forEach(([id, values]) => { + toFulfill[id].resolve(values); + delete toFulfill[id]; + }); + Object.values(toFulfill).forEach((request) => { + request.resolve([]); + }); + }) + .catch((reason) => { + requests.forEach((request) => request.reject(reason)); + }); + } +}; + +// MINMAX PROVIDER +const minMaxProvider = { + supportsRequest: function (domainObject, request) { + return ( + domainObject.type === types.Channel.key && + domainObject.telemetry && + domainObject.telemetry.channelMinMaxUrl && + request.size > 1 && + request.strategy === 'minmax' + ); + }, + batchId: function (domainObject, options) { + return [ + domainObject.telemetry.channelLADUrl, + options.domain, + options.start, + options.end, + options.size + ]; + }, + batchRequest: function (batch) { + const requests = Object.values(batch.requestsById); + const params = requests[0].params; + const options = requests[0].options; + + params.minmax = + '(' + [requests[0].options.size, requests[0].options.domain, 'eu_or_dn'].join(',') + ')'; + params.select = + '(dn,eu,channel_id,ert,scet,sclk,lst,record_type,eu_or_dn,dn_alarm_state,eu_alarm_state)'; + params.filter.channel_id__in = requests.map((req) => req.domainObject.telemetry.channel_id); + setSortFilter(params); + + mcws + .dataTable(requests[0].domainObject.telemetry.channelMinMaxUrl, { + signal: options.signal + }) + .read(params) + .then((res) => { + const valuesByChannelId = groupBy(res, 'channel_id'); + const toFulfill = keyBy(requests, (req) => req.domainObject.telemetry.channel_id); + + Object.entries(valuesByChannelId).forEach(([id, values]) => { + toFulfill[id].resolve(values); + delete toFulfill[id]; + }); + Object.values(toFulfill).forEach((request) => { + request.resolve([]); + }); + }) + .catch((reason) => { + requests.forEach((request) => request.reject(reason)); + }); + }, + isMinMaxProvider: true +}; + +// EVR PROVIDER +const evrProvider = { + supportsRequest: function (domainObject, options) { + const hasTelemetry = Boolean(domainObject.telemetry); + const hasEvrHistoricalUrl = + hasTelemetry && Boolean(domainObject.telemetry.evrHistoricalUrl); + const hasEvrLADUrl = hasTelemetry && Boolean(domainObject.telemetry.evrLADUrl); + + return hasEvrHistoricalUrl || (hasEvrLADUrl && isLADQuery(options)); + }, + request: function (domainObject, options, params) { + const evrHistoricalUrl = domainObject.telemetry.evrHistoricalUrl; + const evrLADUrl = domainObject.telemetry.evrLADUrl; + + let url = evrHistoricalUrl; + + if (evrLADUrl && isLADQuery(options)) { + url = evrLADUrl; + params.lad_type = params.sort; + + /* + * For LAD queries by name, + * MCWS and AMPCS also requires a level + */ + if (domainObject.telemetry.evr_name) { + params.filter.level = '*'; } - }, - { - // MINMAX PROVIDER - supportsRequest: function (domainObject, request) { - return ( - domainObject.type === types.Channel.key && - domainObject.telemetry && - domainObject.telemetry.channelMinMaxUrl && - request.size > 1 && - request.strategy === 'minmax' - ); - }, - batchId: function (domainObject, options) { - return [ - domainObject.telemetry.channelLADUrl, - options.domain, - options.start, - options.end, - options.size - ]; - }, - batchRequest: function (batch) { - const requests = _.values(batch.requestsById); - const params = requests[0].params; - const options = requests[0].options; - - params.minmax = - '(' + [requests[0].options.size, requests[0].options.domain, 'eu_or_dn'].join(',') + ')'; - params.select = - '(dn,eu,channel_id,ert,scet,sclk,lst,record_type,eu_or_dn,dn_alarm_state,eu_alarm_state)'; - params.filter.channel_id__in = _.map(requests, 'domainObject.telemetry.channel_id'); - setSortFilter(params); - - mcws - .dataTable(requests[0].domainObject.telemetry.channelMinMaxUrl, { - signal: options.signal - }) - .read(params) - .then(function (res) { - const valuesByChannelId = _.groupBy(res, 'channel_id'); - const toFulfill = _.keyBy(requests, 'domainObject.telemetry.channel_id'); - - _.forEach(valuesByChannelId, function (values, id) { - toFulfill[id].resolve(values); - delete toFulfill[id]; - }); - _.forEach(toFulfill, function (request) { - request.resolve([]); - }); - }) - .catch((reason) => { - requests.forEach((request) => request.reject(reason)); - }); - }, - isMinMaxProvider: true - }, - { - // EVR PROVIDER - supportsRequest: function (domainObject, options) { - const hasTelemetry = Boolean(domainObject.telemetry); - const hasEvrHistoricalUrl = - hasTelemetry && Boolean(domainObject.telemetry.evrHistoricalUrl); - const hasEvrLADUrl = hasTelemetry && Boolean(domainObject.telemetry.evrLADUrl); - - return hasEvrHistoricalUrl || (hasEvrLADUrl && isLADQuery(options)); - }, - request: function (domainObject, options, params) { - const evrHistoricalUrl = domainObject.telemetry.evrHistoricalUrl; - const evrLADUrl = domainObject.telemetry.evrLADUrl; - - let url = evrHistoricalUrl; - - if (evrLADUrl && isLADQuery(options)) { - url = evrLADUrl; - params.lad_type = params.sort; - - /* - * For LAD queries by name, - * MCWS and AMPCS also requires a level - */ - if (domainObject.telemetry.evr_name) { - params.filter.level = '*'; - } - } + } - if (domainObject.telemetry.level) { - params.filter.level = domainObject.telemetry.level; - } + if (domainObject.telemetry.level) { + params.filter.level = domainObject.telemetry.level; + } - if (domainObject.telemetry.module) { - params.filter.module = domainObject.telemetry.module; - } + if (domainObject.telemetry.module) { + params.filter.module = domainObject.telemetry.module; + } - if (domainObject.telemetry.evr_name) { - params.filter.name = domainObject.telemetry.evr_name; - } + if (domainObject.telemetry.evr_name) { + params.filter.name = domainObject.telemetry.evr_name; + } - setMaxResults(domainObject, options, params); + setMaxResults(domainObject, options, params); - setSortFilter(params); + setSortFilter(params); - return mcws.dataTable(url, { signal: options.signal }).read(params); - } - }, - { - // dataProductProvider - supportsRequest: function (domainObject, options) { - return domainObject.telemetry && !!domainObject.telemetry.dataProductUrl; - }, - request: function (domainObject, options, params) { - setMaxResults(domainObject, options, params); - setSortFilter(params); - - const promise = mcws - .dataTable(domainObject.telemetry.dataProductUrl, { signal: options.signal }) - .read(params); - - if (domainObject.type === 'vista.dataProducts') { - return promise.then(function (results) { - results.forEach(function (datum) { - const sessionId = datum.session_id; - if (datum.unique_name !== undefined) { - const uniqueName = datum.unique_name.replace(/\.dat$/, ''); - const filter = '(session_id=' + sessionId + ',unique_name=' + uniqueName + ')'; - const params = '?filter=' + filter + '&filetype='; - const base_url = domainObject.telemetry.dataProductContentUrl + params; - datum.emd_url = base_url + '.emd'; - datum.emd_preview = base_url + '.emd'; - datum.dat_url = base_url + '.dat'; - datum.txt_url = base_url.replace('DataProductContent', 'DataProductView') + '.dat'; - } - }); - return results; - }); - } - return promise; - } - }, - { - // channel alarm provider - supportsRequest: function (domainObject, options) { - return ( - domainObject.identifier.namespace === 'vista-channel-alarms' && - domainObject.telemetry && - domainObject.telemetry.channelHistoricalUrl && - domainObject.telemetry.alarmLevel - ); - }, - request: function (domainObject, options, params) { - setMaxResults(domainObject, options, params); - setSortFilter(params); + return mcws.dataTable(url, { signal: options.signal }).read(params); + } +}; + +// DATA PRODUCT PROVIDER +const dataProductProvider = { + supportsRequest: function (domainObject, options) { + return domainObject.telemetry && !!domainObject.telemetry.dataProductUrl; + }, + request: function (domainObject, options, params) { + setMaxResults(domainObject, options, params); + setSortFilter(params); + + const promise = mcws + .dataTable(domainObject.telemetry.dataProductUrl, { signal: options.signal }) + .read(params); + + if (domainObject.type === 'vista.dataProducts') { + return promise.then((results) => { + results.forEach((datum) => { + const sessionId = datum.session_id; + if (datum.unique_name !== undefined) { + const uniqueName = datum.unique_name.replace(/\.dat$/, ''); + const filter = '(session_id=' + sessionId + ',unique_name=' + uniqueName + ')'; + const params = '?filter=' + filter + '&filetype='; + const base_url = domainObject.telemetry.dataProductContentUrl + params; + datum.emd_url = base_url + '.emd'; + datum.emd_preview = base_url + '.emd'; + datum.dat_url = base_url + '.dat'; + datum.txt_url = base_url.replace('DataProductContent', 'DataProductView') + '.dat'; + } + }); + return results; + }); + } + return promise; + } +}; - const dnQueryParams = JSON.parse(JSON.stringify(params)); - const euQueryParams = JSON.parse(JSON.stringify(params)); +// CHANNEL ALARM PROVIDER +const channelAlarmProvider = { + supportsRequest: function (domainObject, options) { + return ( + domainObject.identifier.namespace === 'vista-channel-alarms' && + domainObject.telemetry && + domainObject.telemetry.channelHistoricalUrl && + domainObject.telemetry.alarmLevel + ); + }, + request: function (domainObject, options, params) { + setMaxResults(domainObject, options, params); + setSortFilter(params); - if (domainObject.telemetry.alarmLevel === 'any') { - dnQueryParams.filter.dn_alarm_state__in = ['RED', 'YELLOW']; - euQueryParams.filter.eu_alarm_state__in = ['RED', 'YELLOW']; - } else { - dnQueryParams.filter.dn_alarm_state = domainObject.telemetry.alarmLevel.toUpperCase(); + const dnQueryParams = JSON.parse(JSON.stringify(params)); + const euQueryParams = JSON.parse(JSON.stringify(params)); - euQueryParams.filter.eu_alarm_state = domainObject.telemetry.alarmLevel.toUpperCase(); - } + if (domainObject.telemetry.alarmLevel === 'any') { + dnQueryParams.filter.dn_alarm_state__in = ['RED', 'YELLOW']; + euQueryParams.filter.eu_alarm_state__in = ['RED', 'YELLOW']; + } else { + dnQueryParams.filter.dn_alarm_state = domainObject.telemetry.alarmLevel.toUpperCase(); - const dataTable = mcws.dataTable(domainObject.telemetry.channelHistoricalUrl, { - signal: options.signal - }); + euQueryParams.filter.eu_alarm_state = domainObject.telemetry.alarmLevel.toUpperCase(); + } - return Promise.all([dataTable.read(dnQueryParams), dataTable.read(euQueryParams)]).then( - function (results) { - return results[0].concat(results[1]); - } - ); - } - }, - { - // CommandEvents Provider - supportsRequest: function (domainObject, options) { - return domainObject.telemetry && !!domainObject.telemetry.commandEventUrl; - }, - request: function (domainObject, options, params) { - setMaxResults(domainObject, options, params); - params.sort = 'event_time'; - setSortFilter(params); - - if (options.domain === 'ert') { - params.filter.event_time__gte = params.filter[options.domain + '__gte']; - params.filter.event_time__lte = params.filter[options.domain + '__lte']; - } + const dataTable = mcws.dataTable(domainObject.telemetry.channelHistoricalUrl, { + signal: options.signal + }); - delete params.filter[options.domain + '__gte']; - delete params.filter[options.domain + '__lte']; - - return mcws - .dataTable(domainObject.telemetry.commandEventUrl, { signal: options.signal }) - .read(params) - .then( - function (res) { - return res; - }, - function (errorResponse) { - if (errorResponse.status === 400) { - throw errorResponse; - } - - return []; // TODO: better handling due to error. - } - ); - }, - exclusiveDomains: ['ert'] - }, - { - // HEADER CHANNELS HISTORICAL PROVIDER - supportsRequest: function (domainObject, request) { - return ( - domainObject.type === types.HeaderChannel.key && - domainObject.telemetry && - domainObject.telemetry.channelHistoricalUrl - ); - }, - request: function (domainObject, options, params) { - params.filter.channel_id = domainObject.telemetry.channel_id; - setMaxResults(domainObject, options, params); - setSortFilter(params); - - return mcws - .dataTable(domainObject.telemetry.channelHistoricalUrl, { signal: options.signal }) - .read(params); + return Promise.all([dataTable.read(dnQueryParams), dataTable.read(euQueryParams)]).then( + (results) => { + return results[0].concat(results[1]); } + ); + } +}; + +// COMMAND EVENTS PROVIDER +const commandEventsProvider = { + supportsRequest: function (domainObject, options) { + return domainObject.telemetry && !!domainObject.telemetry.commandEventUrl; + }, + request: function (domainObject, options, params) { + setMaxResults(domainObject, options, params); + params.sort = 'event_time'; + setSortFilter(params); + + if (options.domain === 'ert') { + params.filter.event_time__gte = params.filter[options.domain + '__gte']; + params.filter.event_time__lte = params.filter[options.domain + '__lte']; } - ]; - - const channelHistoricalProvider = { - // CHANNEL HISTORICAL PROVIDER - supportsRequest: function (domainObject, request) { - return ( - domainObject.type === types.Channel.key && - domainObject.telemetry && - domainObject.telemetry.channelHistoricalUrl && - (request.strategy === 'minmax' ? !domainObject.telemetry.channelMinMaxUrl : true) && - request.size !== 1 + + delete params.filter[options.domain + '__gte']; + delete params.filter[options.domain + '__lte']; + + return mcws + .dataTable(domainObject.telemetry.commandEventUrl, { signal: options.signal }) + .read(params) + .then( + (res) => { + return res; + }, + (errorResponse) => { + if (errorResponse.status === 400) { + throw errorResponse; + } + + return []; // TODO: better handling due to error. + } ); - }, - batchRequest: function (batch) { - const requests = _.values(batch.requestsById); - const params = requests[0].params; - const options = requests[0].options; - - params.select = - '(dn,eu,channel_id,ert,scet,sclk,lst,record_type,dn_alarm_state,eu_alarm_state,module,realtime,dss_id)'; - params.filter.channel_id__in = _.map(requests, 'domainObject.telemetry.channel_id'); - setSortFilter(params); - - mcws - .dataTable(requests[0].domainObject.telemetry.channelHistoricalUrl, { - signal: options.signal - }) - .read(params) - .then(function (res) { - const valuesByChannelId = _.groupBy(res, 'channel_id'); - const toFulfill = _.keyBy(requests, 'domainObject.telemetry.channel_id'); - - _.forEach(valuesByChannelId, function (values, id) { - toFulfill[id].resolve(values); - delete toFulfill[id]; - }); - _.forEach(toFulfill, function (request) { - request.resolve([]); - }); - }) - .catch((reason) => { - requests.forEach((request) => request.reject(reason)); - }); - }, - request: function (domainObject, options, params) { - params.filter.channel_id = domainObject.telemetry.channel_id; - setSortFilter(params); - setMaxResults(domainObject, options, params); - - return mcws - .dataTable(domainObject.telemetry.channelHistoricalUrl, { signal: options.signal }) - .read(params); - } - }; + }, + exclusiveDomains: ['ert'] +}; - if (window.openmctMCWSConfig?.batchHistoricalChannelQueries === true) { - channelHistoricalProvider.batchId = function (domainObject, options) { - return [domainObject.telemetry.channelHistoricalUrl, options.domain, options.filters]; - }; +// HEADER CHANNELS HISTORICAL PROVIDER +const headerChannelsHistoricalProvider = { + supportsRequest: function (domainObject, request) { + return ( + domainObject.type === types.HeaderChannel.key && + domainObject.telemetry && + domainObject.telemetry.channelHistoricalUrl + ); + }, + request: function (domainObject, options, params) { + params.filter.channel_id = domainObject.telemetry.channel_id; + setMaxResults(domainObject, options, params); + setSortFilter(params); + + return mcws + .dataTable(domainObject.telemetry.channelHistoricalUrl, { signal: options.signal }) + .read(params); } +}; - PROVIDERS.push(channelHistoricalProvider); - - function isLADQuery(options) { - return options.strategy === 'latest'; +// CHANNEL HISTORICAL PROVIDER +const channelHistoricalProvider = { + supportsRequest: function (domainObject, request) { + return ( + domainObject.type === types.Channel.key && + domainObject.telemetry && + domainObject.telemetry.channelHistoricalUrl && + (request.strategy === 'minmax' ? !domainObject.telemetry.channelMinMaxUrl : true) && + request.size !== 1 + ); + }, + batchRequest: function (batch) { + const requests = Object.values(batch.requestsById); + const params = requests[0].params; + const options = requests[0].options; + + params.select = + '(dn,eu,channel_id,ert,scet,sclk,lst,record_type,dn_alarm_state,eu_alarm_state,module,realtime,dss_id)'; + params.filter.channel_id__in = requests.map((req) => req.domainObject.telemetry.channel_id); + setSortFilter(params); + + mcws + .dataTable(requests[0].domainObject.telemetry.channelHistoricalUrl, { + signal: options.signal + }) + .read(params) + .then((res) => { + const valuesByChannelId = groupBy(res, 'channel_id'); + const toFulfill = keyBy(requests, (req) => req.domainObject.telemetry.channel_id); + + Object.entries(valuesByChannelId).forEach(([id, values]) => { + toFulfill[id].resolve(values); + delete toFulfill[id]; + }); + Object.values(toFulfill).forEach((request) => { + request.resolve([]); + }); + }) + .catch((reason) => { + requests.forEach((request) => request.reject(reason)); + }); + }, + request: function (domainObject, options, params) { + params.filter.channel_id = domainObject.telemetry.channel_id; + setSortFilter(params); + setMaxResults(domainObject, options, params); + + return mcws + .dataTable(domainObject.telemetry.channelHistoricalUrl, { signal: options.signal }) + .read(params); } +}; - function setMaxResults(domainObject, options, params) { - if ( - domainObject.telemetry.mcwsVersion >= 3.2 && - options.strategy !== 'comprehensive' && - window.openmctMCWSConfig?.maxResults !== undefined - ) { - params.max_records = window.openmctMCWSConfig.maxResults; - } +if (window.openmctMCWSConfig?.batchHistoricalChannelQueries === true) { + channelHistoricalProvider.batchId = function (domainObject, options) { + return [domainObject.telemetry.channelHistoricalUrl, options.domain, options.filters]; + }; +} + +// Combine all providers into array +const PROVIDERS = [ + channelLADProvider, + minMaxProvider, + evrProvider, + dataProductProvider, + channelAlarmProvider, + commandEventsProvider, + headerChannelsHistoricalProvider, + channelHistoricalProvider +]; + +function isLADQuery(options) { + return options.strategy === 'latest'; +} + +function setMaxResults(domainObject, options, params) { + if ( + domainObject.telemetry.mcwsVersion >= 3.2 && + options.strategy !== 'comprehensive' && + window.openmctMCWSConfig?.maxResults !== undefined + ) { + params.max_records = window.openmctMCWSConfig.maxResults; } +} - function setSortFilter(params) { - if (window.openmctMCWSConfig?.disableSortParam === true) { - delete params.sort; - } +function setSortFilter(params) { + if (window.openmctMCWSConfig?.disableSortParam === true) { + delete params.sort; } +} - function padTime(time) { - if (time < 10) { - return `0${time}`; - } else { - return `${time}`; - } +function padTime(time) { + if (time < 10) { + return `0${time}`; + } else { + return `${time}`; } +} - function HistoricalProvider(openmct) { +class HistoricalProvider { + constructor(openmct) { this.openmct = openmct; this.timeFormatter = UTC_FORMAT_KEY ? this.openmct.telemetry.getFormatter(UTC_FORMAT_KEY) - : new UTCDayOfYearFormat.default(); + : new UTCDayOfYearFormat(); this.clearAlert = this.clearAlert.bind(this); if (window.openmctMCWSConfig?.queryTimespanLimit !== undefined) { - let duration = moment.duration(window.openmctMCWSConfig.queryTimespanLimit, 'milliseconds'); - let hours = padTime(Math.floor(duration.asHours())); - let minutes = padTime(Math.floor(duration.minutes())); - let seconds = padTime(Math.floor(duration.seconds())); + const duration = moment.duration( + window.openmctMCWSConfig.queryTimespanLimit, + 'milliseconds' + ); + const hours = padTime(Math.floor(duration.asHours())); + const minutes = padTime(Math.floor(duration.minutes())); + const seconds = padTime(Math.floor(duration.seconds())); this.formattedQueryTimespanLimit = `${hours}:${minutes}:${seconds} hrs`; } } - HistoricalProvider.prototype.supportsRequest = function (domainObject) { + supportsRequest(domainObject) { return ['vista', 'vista-channel-alarms', 'vista-frame-event-filter'].includes( domainObject.identifier.namespace ); - }; + } - HistoricalProvider.prototype.dispatchBatch = function (provider, batchKey) { + dispatchBatch(provider, batchKey) { const batch = provider.batches[batchKey]; provider.batchRequest(batch); delete provider.batches[batchKey]; - }; + } - HistoricalProvider.prototype.doQueuedRequest = function ( - domainObject, - options, - params, - provider - ) { + doQueuedRequest(domainObject, options, params, provider) { if (!provider.batches) { provider.batches = {}; } @@ -438,11 +475,9 @@ define([ if (!batch) { batch = provider.batches[batchKey] = { requestsById: {}, - dispatch: _.debounce( - function () { - this.dispatchBatch(provider, batchKey); - }.bind(this) - ), + dispatch: debounce(() => { + this.dispatchBatch(provider, batchKey); + }), provider: provider }; } @@ -456,16 +491,16 @@ define([ options: options, params: params }; - entry.promise = new Promise(function (resolve, reject) { + entry.promise = new Promise((resolve, reject) => { entry.resolve = resolve; entry.reject = reject; }); } batch.dispatch(); return entry.promise; - }; + } - HistoricalProvider.prototype.isTimespanLimitExceeded = function (provider, options) { + isTimespanLimitExceeded(provider, options) { const domainsSupported = ['ert', 'scet', 'lmst']; if ( @@ -479,16 +514,14 @@ define([ } return false; - }; + } - HistoricalProvider.prototype.request = function (domainObject, options) { + request(domainObject, options) { let formatter; options = { ...options }; - const provider = PROVIDERS.filter(function (p) { - return p.supportsRequest(domainObject, options); - })[0]; + const provider = PROVIDERS.filter((p) => p.supportsRequest(domainObject, options))[0]; if (!provider) { return Promise.resolve([]); @@ -579,10 +612,10 @@ define([ }); } - const filterService = filterServiceDefault.default(); + const filterServiceInstance = filterService(); - if (filterService) { - const globalFilters = filterService.getActiveFilters(); + if (filterServiceInstance) { + const globalFilters = filterServiceInstance.getActiveFilters(); Object.entries(globalFilters).forEach(([key, filter]) => { const domainObjectFiltersKeys = Object.keys(params.filter); @@ -609,7 +642,7 @@ define([ const responseBody = await errorResponse.text(); const match = responseBody.match(/does not contain the specified parameter column: (\w+)/); - if (match && filterService.hasActiveFilters()) { + if (match && filterServiceInstance.hasActiveFilters()) { this.openmct.notifications.error( `Error requesting telemetry data for ${domainObject.name}: Unsupported filter "${match[1]}". If set, please remove the global filter and retry.` ); @@ -617,10 +650,10 @@ define([ throw errorResponse; } }); - }; + } - HistoricalProvider.prototype.removeFiltersIfAllSelected = function (domainObject, filters) { - let valuesWithFilters = this.openmct.telemetry + removeFiltersIfAllSelected(domainObject, filters) { + const valuesWithFilters = this.openmct.telemetry .getMetadata(domainObject) .values() .filter((metadatum) => metadatum.filters !== undefined) @@ -630,11 +663,11 @@ define([ }, {}); for (const key in filters) { - let metadataFilters = valuesWithFilters[key]; + const metadataFilters = valuesWithFilters[key]; if (metadataFilters) { metadataFilters.forEach((filter) => { if (filter.possibleValues) { - let allSelected = filter.possibleValues.every((possibleValue) => { + const allSelected = filter.possibleValues.every((possibleValue) => { return filters[key].equals && filters[key].equals.includes(possibleValue.value); }); if (allSelected) { @@ -646,9 +679,9 @@ define([ } return filters; - }; + } - HistoricalProvider.prototype.showFiltersWarning = function () { + showFiltersWarning() { //Don't fill the notifications area with lots of warnings. if (!this.filteringAlert) { this.filteringAlert = this.openmct.notifications.alert( @@ -656,33 +689,33 @@ define([ ); this.filteringAlert.on('destroy', this.clearAlert); } - }; + } - HistoricalProvider.prototype.clearAlert = function () { + clearAlert() { this.filteringAlert.off('destroy', this.clearAlert); delete this.filteringAlert; - }; + } - HistoricalProvider.prototype.hasFilters = function (options) { + hasFilters(options) { return ( options.filters !== undefined && Object.values(options.filters).some((filterValue) => { return filterValue && Object.keys(filterValue).length > 0; }) ); - }; + } - HistoricalProvider.prototype.isUnsupportedDomain = function (provider, options) { + isUnsupportedDomain(provider, options) { if (provider.exclusiveDomains && !provider.exclusiveDomains.includes(options.domain)) { return true; } return false; - }; + } - HistoricalProvider.prototype.getSessionService = function () { - return sessionServiceDefault.default(); - }; + getSessionService() { + return sessionService(); + } +} - return HistoricalProvider; -}); +export default HistoricalProvider; diff --git a/src/historical/HistoricalProviderSpec.js b/src/historical/HistoricalProviderSpec.js index 633b49b..6008957 100644 --- a/src/historical/HistoricalProviderSpec.js +++ b/src/historical/HistoricalProviderSpec.js @@ -1,4 +1,4 @@ -import HistoricalProvider from './HistoricalProvider'; +import HistoricalProvider from './HistoricalProvider.js'; describe('Historical Provider', function () { let historicalProvider; diff --git a/src/historical/plugin.js b/src/historical/plugin.js index 0f2f7a1..8c0a4ae 100644 --- a/src/historical/plugin.js +++ b/src/historical/plugin.js @@ -1,21 +1,21 @@ -define(['./HistoricalProvider'], function (HistoricalProvider) { - function HistoricalTelemetryPlugin(options) { - return function install(openmct) { - openmct.telemetry.addProvider(new HistoricalProvider(openmct)); +import HistoricalProvider from './HistoricalProvider.js'; - /** - * Provide a dummy historical provider for message filters to avoid errors in views due to bug. - */ - openmct.telemetry.addProvider({ - supportsRequest: function (domainObject) { - return domainObject.identifier.namespace === 'vista-message-filter'; - }, - request: function (domainObject) { - return Promise.resolve([]); - } - }); - }; - } +function HistoricalTelemetryPlugin(options) { + return function install(openmct) { + openmct.telemetry.addProvider(new HistoricalProvider(openmct)); - return HistoricalTelemetryPlugin; -}); + /** + * Provide a dummy historical provider for message filters to avoid errors in views due to bug. + */ + openmct.telemetry.addProvider({ + supportsRequest: function (domainObject) { + return domainObject.identifier.namespace === 'vista-message-filter'; + }, + request: function (domainObject) { + return Promise.resolve([]); + } + }); + }; +} + +export default HistoricalTelemetryPlugin; diff --git a/src/lib/eventHelpers.js b/src/lib/eventHelpers.js index ec14a36..25f5a0b 100644 --- a/src/lib/eventHelpers.js +++ b/src/lib/eventHelpers.js @@ -1,68 +1,69 @@ -define([], function () { - 'use strict'; +const helperFunctions = { + listenTo(object, event, callback, context) { + if (!this._listeningTo) { + this._listeningTo = []; + } - var helperFunctions = { - listenTo: function (object, event, callback, context) { - if (!this._listeningTo) { - this._listeningTo = []; - } - var listener = { - object: object, - event: event, - callback: callback, - context: context, - _cb: context ? callback.bind(context) : callback - }; - if (object.$watch && event.indexOf('change:') === 0) { - var scopePath = event.replace('change:', ''); - listener.unlisten = object.$watch(scopePath, listener._cb, true); - } else if (object.$on) { - listener.unlisten = object.$on(event, listener._cb); - } else { - object.on(event, listener._cb); - } - this._listeningTo.push(listener); - }, + const listener = { + object: object, + event: event, + callback: callback, + context: context, + _cb: context ? callback.bind(context) : callback + }; - stopListening: function (object, event, callback, context) { - if (!this._listeningTo) { - this._listeningTo = []; - } + if (object.$watch && event.startsWith('change:')) { + const scopePath = event.replace('change:', ''); + listener.unlisten = object.$watch(scopePath, listener._cb, true); + } else if (object.$on) { + listener.unlisten = object.$on(event, listener._cb); + } else { + object.on(event, listener._cb); + } - this._listeningTo - .filter(function (listener) { - if (object && object !== listener.object) { - return false; - } - if (event && event !== listener.event) { - return false; - } - if (callback && callback !== listener.callback) { - return false; - } - if (context && context !== listener.context) { - return false; - } - return true; - }) - .map(function (listener) { - if (listener.unlisten) { - listener.unlisten(); - } else { - listener.object.off(listener.event, listener._cb); - } - return listener; - }) - .forEach(function (listener) { - this._listeningTo.splice(this._listeningTo.indexOf(listener), 1); - }, this); - }, + this._listeningTo.push(listener); + }, - extend: function (object) { - object.listenTo = helperFunctions.listenTo; - object.stopListening = helperFunctions.stopListening; + stopListening(object, event, callback, context) { + if (!this._listeningTo) { + this._listeningTo = []; } - }; - return helperFunctions; -}); + this._listeningTo + .filter((listener) => { + if (object && object !== listener.object) { + return false; + } + if (event && event !== listener.event) { + return false; + } + if (callback && callback !== listener.callback) { + return false; + } + if (context && context !== listener.context) { + return false; + } + + return true; + }) + .map((listener) => { + if (listener.unlisten) { + listener.unlisten(); + } else { + listener.object.off(listener.event, listener._cb); + } + + return listener; + }) + .forEach((listener) => { + this._listeningTo.splice(this._listeningTo.indexOf(listener), 1); + }); + }, + + extend(object) { + object.listenTo = helperFunctions.listenTo; + object.stopListening = helperFunctions.stopListening; + } +}; + +export default helperFunctions; diff --git a/src/lib/extend.js b/src/lib/extend.js index 953253a..0656acf 100644 --- a/src/lib/extend.js +++ b/src/lib/extend.js @@ -1,40 +1,36 @@ -define([], function () { - 'use strict'; +function extend(props) { + const parent = this; + let child; + let Surrogate; + + if (props && Object.hasOwn(props, 'constructor')) { + child = props.constructor; + } else { + child = function () { + return parent.apply(this, arguments); + }; + } - function extend(props) { - var parent = this, - child, - Surrogate; + Object.keys(parent).forEach((propKey) => { + child[propKey] = parent[propKey]; + }); - if (props && Object.hasOwn(props, 'constructor')) { - child = props.constructor; - } else { - child = function () { - return parent.apply(this, arguments); - }; - } + // Surrogate allows inheriting from parent without invoking constructor. + Surrogate = function () { + this.constructor = child; + }; + Surrogate.prototype = parent.prototype; + child.prototype = new Surrogate(); - Object.keys(parent).forEach(function copyStaticProperties(propKey) { - child[propKey] = parent[propKey]; + if (props) { + Object.keys(props).forEach((key) => { + child.prototype[key] = props[key]; }); + } - // Surrogate allows inheriting from parent without invoking constructor. - Surrogate = function () { - this.constructor = child; - }; - Surrogate.prototype = parent.prototype; - child.prototype = new Surrogate(); - - if (props) { - Object.keys(props).forEach(function copyInstanceProperties(key) { - child.prototype[key] = props[key]; - }); - } - - child.__super__ = parent.prototype; + child.__super__ = parent.prototype; - return child; - } + return child; +} - return extend; -}); +export default extend; diff --git a/src/link/plugin.js b/src/link/plugin.js index 1c8780e..42c5b96 100644 --- a/src/link/plugin.js +++ b/src/link/plugin.js @@ -1,46 +1,46 @@ -define(['@braintree/sanitize-url'], function (urlSanitizeLib) { - function LinkPlugin() { - return function install(openmct) { - openmct.types.addType('vista.link', { - name: 'Hyperlink', - description: 'A link to another page in VISTA or an external resource.', - cssClass: 'icon-activity', - initialize: function (obj) { - return obj; - }, - creatable: true, - form: [ - { - name: 'URL', - key: 'url', - control: 'textfield', - cssClass: 'l-input-lg' - } - ] - }); +import urlSanitizeLib from '@braintree/sanitize-url'; - openmct.objectViews.addProvider({ - key: 'view.link', - canView: function (domainObject) { - return domainObject.type === 'vista.link'; - }, - view: function (domainObject) { - return { - show: function (container) { - container.textContent = ''; +function LinkPlugin() { + return function install(openmct) { + openmct.types.addType('vista.link', { + name: 'Hyperlink', + description: 'A link to another page in VISTA or an external resource.', + cssClass: 'icon-activity', + initialize: function (obj) { + return obj; + }, + creatable: true, + form: [ + { + name: 'URL', + key: 'url', + control: 'textfield', + cssClass: 'l-input-lg' + } + ] + }); - const anchor = document.createElement('a'); - anchor.href = urlSanitizeLib.sanitizeUrl(domainObject.url); - anchor.textContent = domainObject.name; + openmct.objectViews.addProvider({ + key: 'view.link', + canView: function (domainObject) { + return domainObject.type === 'vista.link'; + }, + view: function (domainObject) { + return { + show: function (container) { + container.textContent = ''; - container.appendChild(anchor); - }, - destroy: function () {} - }; - } - }); - }; - } + const anchor = document.createElement('a'); + anchor.href = urlSanitizeLib.sanitizeUrl(domainObject.url); + anchor.textContent = domainObject.name; + + container.appendChild(anchor); + }, + destroy: function () {} + }; + } + }); + }; +} - return LinkPlugin; -}); +export default LinkPlugin; diff --git a/src/mcwsIndicator/MCWSIndicator.vue b/src/mcwsIndicator/MCWSIndicator.vue index 6ea8f4b..8f6731c 100644 --- a/src/mcwsIndicator/MCWSIndicator.vue +++ b/src/mcwsIndicator/MCWSIndicator.vue @@ -7,7 +7,7 @@ - - - - - - - - - -
- - diff --git a/instances/development/instance.yaml b/instances/development/instance.yaml index cc16697..fc3011a 100644 --- a/instances/development/instance.yaml +++ b/instances/development/instance.yaml @@ -1,6 +1,6 @@ # yaml-language-server: $schema=assets/openmct-configuration-schema.json openmct: - version: latest + npmPackage: nasa/openmct#omm-r5.4.0-rc4 plugins: - mct-bootstrap-plugin: npmPackage: file:./assets/mct-bootstrap-plugin diff --git a/instances/development/package.json b/instances/development/package.json index eb7f9b5..ec6dd4a 100644 --- a/instances/development/package.json +++ b/instances/development/package.json @@ -11,7 +11,7 @@ "license": "ISC", "devDependencies": { "mct-bootstrap-plugin": "file:assets/mct-bootstrap-plugin", - "openmct": "^4.1.0", + "openmct": "github:nasa/openmct#omm-r5.4.0-rc4", "openmct-mcws": "file:../..", "openmct-mcws-plugin": "file:../.." } diff --git a/plugin.js b/plugin.js index fc6c538..d923537 100644 --- a/plugin.js +++ b/plugin.js @@ -259,6 +259,12 @@ export default function openmctMCWSPlugin(options) { window.openmctMCWSConfig = config; // window.openmct = openmct; }); + + // load the css file + const link = document.createElement('link'); + link.rel = 'stylesheet'; + link.href = `node_modules/openmct-mcws-plugin/dist/openmct-mcws-plugin.css`; + document.head.appendChild(link); } /** diff --git a/recipes/development.yaml b/recipes/development.yaml index 2e7a4d9..e172401 100644 --- a/recipes/development.yaml +++ b/recipes/development.yaml @@ -1,6 +1,6 @@ # yaml-language-server: $schema=../src/assets/openmct-configuration-schema.json openmct: - version: 'latest' + npmPackage: nasa/openmct#omm-r5.4.0-rc4 plugins: - openmct.plugins.Snow # Theme: 'Snow', 'Espresso' or 'Maelstrom' - openmct.plugins.ObjectMigration diff --git a/src/services/mcws/MCWSClient.js b/src/services/mcws/MCWSClient.js index 9c5c6d5..ee8448c 100644 --- a/src/services/mcws/MCWSClient.js +++ b/src/services/mcws/MCWSClient.js @@ -41,8 +41,10 @@ class MCWSClient { // Preserve the isJsonResponse flag for baseRequest if (isJsonResponse) { - options.params = { output: 'json' }; + options.isJsonResponse = true; } + + delete options.params; } options.url = url; @@ -58,13 +60,16 @@ class MCWSClient { async baseRequest(url, options) { let response; let isJsonResponse = false; + + if (options?.isJsonResponse) { + isJsonResponse = true; + delete options.isJsonResponse; + } + + this.pending++; if (options?.params) { - if (options.params?.output === 'json') { - isJsonResponse = true; - } - const params = new URLSearchParams(options.params); // append options params to url From 9b93d5ba9555830870234c68617f37952bbaa804 Mon Sep 17 00:00:00 2001 From: Jamie V Date: Mon, 24 Nov 2025 11:12:43 -0800 Subject: [PATCH 04/22] remove instances --- .../default/assets/installCommonJsPlugin.js | 24 ---- instances/default/assets/installEs6Plugin.js | 17 --- instances/default/assets/load-umd.js | 29 ---- .../assets/mct-bootstrap-plugin/an-asset.txt | 1 - .../assets/mct-bootstrap-plugin/package.json | 12 -- .../assets/mct-bootstrap-plugin/plugin.js | 48 ------- .../assets/openmct-configuration-schema.json | 111 ---------------- instances/default/index.html | 30 ----- instances/default/instance.yaml | 74 ----------- instances/default/package.json | 17 --- .../assets/installBuiltinPlugin.js | 9 -- .../assets/installCommonJsPlugin.js | 36 ----- .../development/assets/installEs6Plugin.js | 24 ---- instances/development/assets/load-umd.js | 29 ---- .../assets/mct-bootstrap-plugin/an-asset.txt | 1 - .../assets/mct-bootstrap-plugin/package.json | 12 -- .../assets/mct-bootstrap-plugin/plugin.js | 48 ------- .../development/assets/mct-builder-core.d.ts | 27 ---- .../development/assets/mct-builder-core.js | 114 ---------------- .../assets/openmct-configuration-schema.json | 111 ---------------- instances/development/index.html | 34 ----- instances/development/instance.yaml | 124 ------------------ instances/development/package.json | 18 --- 23 files changed, 950 deletions(-) delete mode 100644 instances/default/assets/installCommonJsPlugin.js delete mode 100644 instances/default/assets/installEs6Plugin.js delete mode 100644 instances/default/assets/load-umd.js delete mode 100644 instances/default/assets/mct-bootstrap-plugin/an-asset.txt delete mode 100644 instances/default/assets/mct-bootstrap-plugin/package.json delete mode 100644 instances/default/assets/mct-bootstrap-plugin/plugin.js delete mode 100644 instances/default/assets/openmct-configuration-schema.json delete mode 100644 instances/default/index.html delete mode 100644 instances/default/instance.yaml delete mode 100644 instances/default/package.json delete mode 100644 instances/development/assets/installBuiltinPlugin.js delete mode 100644 instances/development/assets/installCommonJsPlugin.js delete mode 100644 instances/development/assets/installEs6Plugin.js delete mode 100644 instances/development/assets/load-umd.js delete mode 100644 instances/development/assets/mct-bootstrap-plugin/an-asset.txt delete mode 100644 instances/development/assets/mct-bootstrap-plugin/package.json delete mode 100644 instances/development/assets/mct-bootstrap-plugin/plugin.js delete mode 100644 instances/development/assets/mct-builder-core.d.ts delete mode 100644 instances/development/assets/mct-builder-core.js delete mode 100644 instances/development/assets/openmct-configuration-schema.json delete mode 100644 instances/development/index.html delete mode 100644 instances/development/instance.yaml delete mode 100644 instances/development/package.json diff --git a/instances/default/assets/installCommonJsPlugin.js b/instances/default/assets/installCommonJsPlugin.js deleted file mode 100644 index ed07d2f..0000000 --- a/instances/default/assets/installCommonJsPlugin.js +++ /dev/null @@ -1,24 +0,0 @@ -import loadUmd from './load-umd.js'; -export default async function installCommonJsPlugin({openmct, importPath, installFunctionName, installFunctionOptions}) { - const imports = await loadUmd(importPath); - if (typeof imports === 'function') { - const installFunction = imports; - openmct.install(installFunction(installFunctionOptions)); - } else if (typeof imports === 'object') { - const exportedFunctionNames = Object.keys(imports); - if (exportedFunctionNames.length === 1) { - const resolvedInstallFunctionName = exportedFunctionNames[0]; - const installFunction = imports[resolvedInstallFunctionName]; - openmct.install(installFunction(installFunctionOptions)); - } else { - const exportedFunctionMap = exportedFunctionNames.reduce((map, key) => { - map.set(key.toLowerCase().replaceAll(/[^a-z0-9]/g, ''), imports[key]); - return map; - }, new Map()); - const installFunction = exportedFunctionMap.get(installFunctionName.toLowerCase().replaceAll(/[^a-z0-9]/g, '')); - openmct.install(installFunction(installFunctionOptions)); - } - } else { - console.error(`Unsupported import type for ${importPath}`); - } -} \ No newline at end of file diff --git a/instances/default/assets/installEs6Plugin.js b/instances/default/assets/installEs6Plugin.js deleted file mode 100644 index ea26b1b..0000000 --- a/instances/default/assets/installEs6Plugin.js +++ /dev/null @@ -1,17 +0,0 @@ -export default async function installEs6Plugin({openmct, importPath, installFunctionName, installFunctionOptions}) { - const imports = await import(importPath); - const exportedNames = Object.keys(imports); - // If only one export, assume it is the install function. This simplifies things for the 90% case of a single default export - if (exportedNames.length === 1) { - const resolvedInstallFunctionName = exportedNames[0]; - const installFunction = imports[resolvedInstallFunctionName]; - openmct.install(installFunction(installFunctionOptions)); - } else { - const exportedFunctionMap = Object.keys(imports).reduce((map, key) => { - map.set(key.toLowerCase().replaceAll(/[^a-z0-9]/g, ''), imports[key]); - return map; - }, new Map()); - const installFunction = exportedFunctionMap.get(installFunctionName.toLowerCase().replaceAll(/[^a-z0-9]/g, '')); - openmct.install(installFunction(installFunctionOptions)); - } -} \ No newline at end of file diff --git a/instances/default/assets/load-umd.js b/instances/default/assets/load-umd.js deleted file mode 100644 index 00cc87b..0000000 --- a/instances/default/assets/load-umd.js +++ /dev/null @@ -1,29 +0,0 @@ -const savedModule = window.module; -const savedExports = window.savedExports; - -export default async function loadUmd(src) { - const mockExports = {}; - const mockModule = {exports: mockExports}; - - window.module = mockModule; - window.exports = mockExports; - - try { - await import(src); - const exports = window.module?.exports || window.exports; - - return exports; - } finally { - if (savedExports === undefined) { - delete window.exports; - } else { - window.exports = savedExports; - } - - if (savedModule === undefined) { - delete window.module; - } else { - window.module = savedModule; - } - } -} \ No newline at end of file diff --git a/instances/default/assets/mct-bootstrap-plugin/an-asset.txt b/instances/default/assets/mct-bootstrap-plugin/an-asset.txt deleted file mode 100644 index c29d8ab..0000000 --- a/instances/default/assets/mct-bootstrap-plugin/an-asset.txt +++ /dev/null @@ -1 +0,0 @@ -This is an asset! \ No newline at end of file diff --git a/instances/default/assets/mct-bootstrap-plugin/package.json b/instances/default/assets/mct-bootstrap-plugin/package.json deleted file mode 100644 index a01f169..0000000 --- a/instances/default/assets/mct-bootstrap-plugin/package.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "mct-bootstrap-plugin", - "version": "0.0.1", - "description": "Bootstraps and initializes Open MCT", - "main": "plugin.js", - "type": "module", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "author": "National Aeronautics and Space Administration", - "license": "UNLICENSED" -} diff --git a/instances/default/assets/mct-bootstrap-plugin/plugin.js b/instances/default/assets/mct-bootstrap-plugin/plugin.js deleted file mode 100644 index 201752d..0000000 --- a/instances/default/assets/mct-bootstrap-plugin/plugin.js +++ /dev/null @@ -1,48 +0,0 @@ -const simpleTimeMathRegex = /^(now)?\s*([-+]?\s*\d+)?$/; - -function getEpochTime(timeExpression) { - const now = Date.now(); - const regexResult = simpleTimeMathRegex.exec(timeExpression); - if (regexResult) { - const isOffsetFromNow = regexResult[1] === 'now'; - if (isOffsetFromNow) { - const offsetString = regexResult[2] || '0'; - const offset = parseInt(offsetString.replaceAll(' ', ''), 10); - return now + offset; - - } - return timeExpression; - } - return timeExpression; -} -export function helloPanda() { - return function install(openmct) { - openmct.once('start', () => { - alert('hello panda'); - }); - } -} -export function testRelativeAssetPaths({testAsset}) { - return function install(openmct) { - openmct.once('start', async () => { - const asset = await fetch(testAsset); - const assetText = await asset.text(); - console.log(`Asset text: ${assetText}`); - }); - } -} -export function mctBootstrapPlugin({timeSystem, clock, start, end, startOffset, endOffset, mode} = {timeSystem: 'utc', clock: 'local', start: 'now - 900000', end: 'now', startOffset: -60000, endOffset: 0, mode: 'realtime'}) { - return function install(openmct) { - openmct.install(openmct.plugins.UTCTimeSystem()); - openmct.time.setTimeSystem(timeSystem); - openmct.time.setClock(clock); - - if (mode === 'fixed') { - start = getEpochTime(start); - end = getEpochTime(end); - openmct.time.setMode(mode, {start, end}); - } else { - openmct.time.setMode(mode, {start: startOffset, end: endOffset}); - } - } -} \ No newline at end of file diff --git a/instances/default/assets/openmct-configuration-schema.json b/instances/default/assets/openmct-configuration-schema.json deleted file mode 100644 index b3d87c9..0000000 --- a/instances/default/assets/openmct-configuration-schema.json +++ /dev/null @@ -1,111 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "OpenMct Configuration Schema", - "description": "Schema for OpenMCT configuration YAML", - "definitions": { - "pluginMap": { - "type": "object", - "patternProperties": { - "^.*$": { - "$ref": "#/definitions/plugin" - } - } - }, - "plugin": { - "type": "object", - "properties": { - "source": { - "type": "string", - "enum": ["npm", "builtin"], - "default": "builtin" - }, - "npmPackage": { - "type": "string", - "description": "NPM package that provides this plugin." - }, - "installFunction": { - "type": "string", - "description": "Name of the function that is exported from the npm package and which provides this plugin." - }, - "entryPoint": { - "type": "string", - "description": "Script that exports the install function(s)" - }, - "enabled": { - "type": "boolean", - "description": "Whether the plugin should be enabled. By setting this to false you can override default plugins" - }, - "options": { - "oneOf": [ - { - "type": "object", - "description": "Options to configure the plugin" - }, - { - "type": "array", - "description": "Arguments to be provided to a plugin install function" - } - ], - "description": "The options to be passed to the plugin at install time. Can be either an object or an array. If an array is provided, each member will be treated as an argument to the plugin install function." - } - }, - "additionalProperties": true - } - }, - "type": "object", - "required": ["openmct"], - "properties": { - "openmct": { - "type": "object", - "oneOf": [ - { - "properties": { - "version": { - "type": "string", - "description": "Version of OpenMCT to use", - "default": "latest" - }, - "plugins": { - "type": "array", - "items": { - "oneOf": [ - { - "type": "string", - "description": "Plugin identifier (e.g., 'openmct.Plugins.LocalStorage')" - },{ - "$ref": "#/definitions/pluginMap" - } - ] - } - } - }, - "additionalProperties": false, - "required": ["version"] - }, - { - "properties": { - "npmPackage": { - "type": "string", - "description": "NPM package that provides Open MCT. If present this will override any Open MCT version specified." - }, - "plugins": { - "type": "array", - "items": { - "oneOf": [ - { - "type": "string", - "description": "Plugin identifier (e.g., 'openmct.Plugins.LocalStorage')" - },{ - "$ref": "#/definitions/pluginMap" - } - ] - } - } - }, - "additionalProperties": false, - "required": ["npmPackage"] - } - ] - } - } - } \ No newline at end of file diff --git a/instances/default/index.html b/instances/default/index.html deleted file mode 100644 index 1b36bee..0000000 --- a/instances/default/index.html +++ /dev/null @@ -1,30 +0,0 @@ - - Open MCT - - - - - - - - - \ No newline at end of file diff --git a/instances/default/instance.yaml b/instances/default/instance.yaml deleted file mode 100644 index 3e4bea6..0000000 --- a/instances/default/instance.yaml +++ /dev/null @@ -1,74 +0,0 @@ -# yaml-language-server: $schema=assets/openmct-configuration-schema.json -openmct: - version: latest - plugins: - - mct-bootstrap-plugin: - npmPackage: file:./assets/mct-bootstrap-plugin - options: - timeSystem: utc - clock: local - startOffset: -60000 - endOffset: 0 - mode: realtime - - openmct.plugins.Espresso - - openmct.plugins.MyItems - - openmct.plugins.LocalStorage - - openmct.plugins.UTCTimeSystem - - openmct.plugins.PlanLayout: - options: - creatable: true - - openmct.plugins.DisplayLayout: - options: - showAsView: - - summary-widget - - vista.packetSummaryEvents - - vista.dataProducts - - vista.packets - - vista.frameSummary - - vista.frameWatch - - openmct.plugins.Snow - - openmct.plugins.ObjectMigration - - openmct.plugins.ClearData: - options: - - - table - - telemetry.plot.overlay - - telemetry.plot.stacked - - vista.packetSummaryEvents - - vista.dataProducts - - vista.packets - - vista.frameSummary - - vista.frameWatch - - vista.chanTableGroup - - indicator: false - - openmct.plugins.Filters: - options: - - vista.alarmsView - - telemetry.plot.overlay - - table - - vista.chanTableGroup - - vista.commandEventsView - - vista.messagesView - - vista.evrView - - openmct.plugins.Notebook - - openmct.plugins.Clock: - options: - useClockIndicator: false - - openmct.plugins.DefaultRootName: - options: - - VISTA - - openmct-mcws-plugin: - npmPackage: file:/Users/jjviglio/OpenMCT/OMM/openmct-mcws/ - entryPoint: dist/openmct-mcws-plugin.js - options: - useDeveloperStorage: true - proxyUrl: http://localhost:8080/ - camUrl: '' - mcwsUrl: '' - namespaces: - - key: r50-dev - name: R5.0 Shared - url: '' - - userNamespace: true - key: r50-dev - name: R5.0 Users - url: '' diff --git a/instances/default/package.json b/instances/default/package.json deleted file mode 100644 index 002aa25..0000000 --- a/instances/default/package.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "default", - "version": "1.0.0", - "description": "", - "main": "index.js", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "keywords": [], - "author": "", - "license": "ISC", - "devDependencies": { - "mct-bootstrap-plugin": "file:assets/mct-bootstrap-plugin", - "openmct": "^4.1.0", - "openmct-mcws": "file:../.." - } -} diff --git a/instances/development/assets/installBuiltinPlugin.js b/instances/development/assets/installBuiltinPlugin.js deleted file mode 100644 index 872d818..0000000 --- a/instances/development/assets/installBuiltinPlugin.js +++ /dev/null @@ -1,9 +0,0 @@ -import { substituteVariables, runtimeSubstitutions } from "./mct-builder-core.js"; - -export default async function installBuiltinPlugin({openmct, installFunction, installFunctionOptions, buildTimeSubstitutions}) { - const optionsWithSubstitutions = substituteVariables(installFunctionOptions, { - ...buildTimeSubstitutions, - ...runtimeSubstitutions - }); - openmct.install(installFunction(optionsWithSubstitutions)); -} \ No newline at end of file diff --git a/instances/development/assets/installCommonJsPlugin.js b/instances/development/assets/installCommonJsPlugin.js deleted file mode 100644 index 5b0092c..0000000 --- a/instances/development/assets/installCommonJsPlugin.js +++ /dev/null @@ -1,36 +0,0 @@ -import {loadUmd, substituteVariables, getEpochTime} from './mct-builder-core.js'; - -const runtimeSubstitutions = { - '/(.*)(\\${now})(.*)/': (originalProperty) => { - return getEpochTime(originalProperty); - } -} - -export default async function installCommonJsPlugin({openmct, importPath, installFunctionName, installFunctionOptions, buildTimeSubstitutions}) { - const optionsWithSubstitutions = substituteVariables(installFunctionOptions, { - ...buildTimeSubstitutions, - ...runtimeSubstitutions, - }); - - const imports = await loadUmd(importPath); - if (typeof imports === 'function') { - const installFunction = imports; - openmct.install(installFunction(optionsWithSubstitutions)); - } else if (typeof imports === 'object') { - const exportedFunctionNames = Object.keys(imports); - if (exportedFunctionNames.length === 1) { - const resolvedInstallFunctionName = exportedFunctionNames[0]; - const installFunction = imports[resolvedInstallFunctionName]; - openmct.install(installFunction(optionsWithSubstitutions)); - } else { - const exportedFunctionMap = exportedFunctionNames.reduce((map, key) => { - map.set(key.toLowerCase().replaceAll(/[^a-z0-9]/g, ''), imports[key]); - return map; - }, new Map()); - const installFunction = exportedFunctionMap.get(installFunctionName.toLowerCase().replaceAll(/[^a-z0-9]/g, '')); - openmct.install(installFunction(optionsWithSubstitutions)); - } - } else { - console.error(`Unsupported import type for ${importPath}`); - } -} \ No newline at end of file diff --git a/instances/development/assets/installEs6Plugin.js b/instances/development/assets/installEs6Plugin.js deleted file mode 100644 index 6a25466..0000000 --- a/instances/development/assets/installEs6Plugin.js +++ /dev/null @@ -1,24 +0,0 @@ -import { substituteVariables, runtimeSubstitutions } from "./mct-builder-core.js"; - -export default async function installEs6Plugin({openmct, importPath, installFunctionName, installFunctionOptions, buildTimeSubstitutions}) { - const imports = await import(importPath); - const exportedNames = Object.keys(imports); - - const optionsWithSubstitutions = substituteVariables(installFunctionOptions, { - ...buildTimeSubstitutions, - ...runtimeSubstitutions - }); - // If only one export, assume it is the install function. This simplifies things for the 90% case of a single default export - if (exportedNames.length === 1) { - const resolvedInstallFunctionName = exportedNames[0]; - const installFunction = imports[resolvedInstallFunctionName]; - openmct.install(installFunction(optionsWithSubstitutions)); - } else { - const exportedFunctionMap = Object.keys(imports).reduce((map, key) => { - map.set(key.toLowerCase().replaceAll(/[^a-z0-9]/g, ''), imports[key]); - return map; - }, new Map()); - const installFunction = exportedFunctionMap.get(installFunctionName.toLowerCase().replaceAll(/[^a-z0-9]/g, '')); - openmct.install(installFunction(optionsWithSubstitutions)); - } -} \ No newline at end of file diff --git a/instances/development/assets/load-umd.js b/instances/development/assets/load-umd.js deleted file mode 100644 index 00cc87b..0000000 --- a/instances/development/assets/load-umd.js +++ /dev/null @@ -1,29 +0,0 @@ -const savedModule = window.module; -const savedExports = window.savedExports; - -export default async function loadUmd(src) { - const mockExports = {}; - const mockModule = {exports: mockExports}; - - window.module = mockModule; - window.exports = mockExports; - - try { - await import(src); - const exports = window.module?.exports || window.exports; - - return exports; - } finally { - if (savedExports === undefined) { - delete window.exports; - } else { - window.exports = savedExports; - } - - if (savedModule === undefined) { - delete window.module; - } else { - window.module = savedModule; - } - } -} \ No newline at end of file diff --git a/instances/development/assets/mct-bootstrap-plugin/an-asset.txt b/instances/development/assets/mct-bootstrap-plugin/an-asset.txt deleted file mode 100644 index c29d8ab..0000000 --- a/instances/development/assets/mct-bootstrap-plugin/an-asset.txt +++ /dev/null @@ -1 +0,0 @@ -This is an asset! \ No newline at end of file diff --git a/instances/development/assets/mct-bootstrap-plugin/package.json b/instances/development/assets/mct-bootstrap-plugin/package.json deleted file mode 100644 index a01f169..0000000 --- a/instances/development/assets/mct-bootstrap-plugin/package.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "mct-bootstrap-plugin", - "version": "0.0.1", - "description": "Bootstraps and initializes Open MCT", - "main": "plugin.js", - "type": "module", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "author": "National Aeronautics and Space Administration", - "license": "UNLICENSED" -} diff --git a/instances/development/assets/mct-bootstrap-plugin/plugin.js b/instances/development/assets/mct-bootstrap-plugin/plugin.js deleted file mode 100644 index 201752d..0000000 --- a/instances/development/assets/mct-bootstrap-plugin/plugin.js +++ /dev/null @@ -1,48 +0,0 @@ -const simpleTimeMathRegex = /^(now)?\s*([-+]?\s*\d+)?$/; - -function getEpochTime(timeExpression) { - const now = Date.now(); - const regexResult = simpleTimeMathRegex.exec(timeExpression); - if (regexResult) { - const isOffsetFromNow = regexResult[1] === 'now'; - if (isOffsetFromNow) { - const offsetString = regexResult[2] || '0'; - const offset = parseInt(offsetString.replaceAll(' ', ''), 10); - return now + offset; - - } - return timeExpression; - } - return timeExpression; -} -export function helloPanda() { - return function install(openmct) { - openmct.once('start', () => { - alert('hello panda'); - }); - } -} -export function testRelativeAssetPaths({testAsset}) { - return function install(openmct) { - openmct.once('start', async () => { - const asset = await fetch(testAsset); - const assetText = await asset.text(); - console.log(`Asset text: ${assetText}`); - }); - } -} -export function mctBootstrapPlugin({timeSystem, clock, start, end, startOffset, endOffset, mode} = {timeSystem: 'utc', clock: 'local', start: 'now - 900000', end: 'now', startOffset: -60000, endOffset: 0, mode: 'realtime'}) { - return function install(openmct) { - openmct.install(openmct.plugins.UTCTimeSystem()); - openmct.time.setTimeSystem(timeSystem); - openmct.time.setClock(clock); - - if (mode === 'fixed') { - start = getEpochTime(start); - end = getEpochTime(end); - openmct.time.setMode(mode, {start, end}); - } else { - openmct.time.setMode(mode, {start: startOffset, end: endOffset}); - } - } -} \ No newline at end of file diff --git a/instances/development/assets/mct-builder-core.d.ts b/instances/development/assets/mct-builder-core.d.ts deleted file mode 100644 index b31b9e1..0000000 --- a/instances/development/assets/mct-builder-core.d.ts +++ /dev/null @@ -1,27 +0,0 @@ - -type SubstitutionFunction = (originalProperty: string, regexResult: RegExpExecArray) => string; - -declare module '../assets/mct-builder-core.js' { - /** - * Substitutes variables in the input object based on the provided variables - * @param pluginOptions The input object to process - * @param variables Key-value pairs of variables to substitute. If a function is defined, the function will be called to generate the substituted value - * - * @returns The input object with variables substituted - */ - export function substituteVariables(pluginOptions: object, variables: Record): T; - - /** - * Parses a time expression and returns the corresponding epoch time - * @param timeExpression Time expression to parse (e.g., '${now}' or '${now}+1000') - * - * @returns The time in the JavaScript epoch (in ms) corresponding to the time expression - */ - export function getEpochTime(timeExpression: string): number; - - /** - * Loads a UMD module - * @param src Source URL or path of the module to load - */ - export function loadUmd(src: string): Promise; -} \ No newline at end of file diff --git a/instances/development/assets/mct-builder-core.js b/instances/development/assets/mct-builder-core.js deleted file mode 100644 index afbb35c..0000000 --- a/instances/development/assets/mct-builder-core.js +++ /dev/null @@ -1,114 +0,0 @@ -const savedModule = window.module; -const savedExports = window.savedExports; - -export function substituteVariables(input, variables) { - if (input === undefined || variables === undefined) { - return undefined; - } - return JSON.parse(JSON.stringify(input, (instanceConfigurationKey, instanceConfigurationValue) => { - let result = instanceConfigurationValue; - - if (typeof instanceConfigurationValue === 'string') { - for (const [replacementVariableKey, replacementVariableValue] of Object.entries(variables)) { - if (replacementVariableKey.startsWith('/')) { - const regex = new RegExp(replacementVariableKey.slice(1, replacementVariableKey.length - 1)); - if (regex.test(result)) { - if (typeof replacementVariableValue === 'function') { - result = replacementVariableValue(result, regex.exec(result)); - } - } - } else if (result.includes(replacementVariableKey)){ - result = result.replaceAll(replacementVariableKey, replacementVariableValue); - } - } - } - - return result; - })); -} - -export async function loadUmd(src) { - const mockExports = {}; - const mockModule = {exports: mockExports}; - - window.module = mockModule; - window.exports = mockExports; - - try { - await import(src); - const exports = window.module?.exports || window.exports; - - return exports; - } finally { - if (savedExports === undefined) { - delete window.exports; - } else { - window.exports = savedExports; - } - - if (savedModule === undefined) { - delete window.module; - } else { - window.module = savedModule; - } - } -} -const timeMathSubstitutionsInternal = { - 'now': () => { - return Date.now(); - }, - 'five_seconds': 5000, - 'ten_seconds': 10000, - 'fifteen_seconds': 15000, - 'thirty_seconds': 30000, - 'one_minute': 60000, - 'five_minutes': 300000, - 'ten_minutes': 600000, - 'fifteen_minutes': 900000, - 'thirty_minutes': 1800000, - 'one_hour': 3600000, - 'two_hours': 7200000, - 'one_day': 86400000, - 'one_week': 604800000, - 'one_month': 2592000000, - 'one_year': 31536000000, - 'two_years': 63072000000, - 'five_years': 157680000000, - 'ten_years': 315360000000 -} -const joined = Object.keys(timeMathSubstitutionsInternal).join('|'); -const regex = `/\\\${(${joined})}/`; -const compiledRegex = new RegExp(regex.slice(1, regex.length - 1), 'g'); -const runtimeSubstitutionsInternal = {}; -runtimeSubstitutionsInternal[regex] = (originalProperty) => { - return getEpochTime(originalProperty); -}; -const simpleTimeMathRegex = /^(\d+)?\s*([-+]?\s*\d+)?$/; - -export const runtimeSubstitutions = runtimeSubstitutionsInternal; -export const timeMathSubstitutions = timeMathSubstitutionsInternal; - -export function getEpochTime(timeExpression) { - timeExpression = timeExpression.replaceAll(compiledRegex, (match, p1) => { - const timeSubstitution = timeMathSubstitutions[p1]; - if (typeof timeSubstitution === 'function') { - return timeSubstitution(); - } else { - return timeSubstitution; - } - }); - return parseSimpleTimeMath(timeExpression); -} - -function parseSimpleTimeMath(timeExpression) { - const match = simpleTimeMathRegex.exec(timeExpression); - if (match) { - return match.slice(1).reduce((acc, val) => { - if (val === undefined) { - return acc; - } else { - return acc + parseInt(val.replaceAll(' ', '')) || 0; - } - }, 0); - } -} \ No newline at end of file diff --git a/instances/development/assets/openmct-configuration-schema.json b/instances/development/assets/openmct-configuration-schema.json deleted file mode 100644 index b3d87c9..0000000 --- a/instances/development/assets/openmct-configuration-schema.json +++ /dev/null @@ -1,111 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "OpenMct Configuration Schema", - "description": "Schema for OpenMCT configuration YAML", - "definitions": { - "pluginMap": { - "type": "object", - "patternProperties": { - "^.*$": { - "$ref": "#/definitions/plugin" - } - } - }, - "plugin": { - "type": "object", - "properties": { - "source": { - "type": "string", - "enum": ["npm", "builtin"], - "default": "builtin" - }, - "npmPackage": { - "type": "string", - "description": "NPM package that provides this plugin." - }, - "installFunction": { - "type": "string", - "description": "Name of the function that is exported from the npm package and which provides this plugin." - }, - "entryPoint": { - "type": "string", - "description": "Script that exports the install function(s)" - }, - "enabled": { - "type": "boolean", - "description": "Whether the plugin should be enabled. By setting this to false you can override default plugins" - }, - "options": { - "oneOf": [ - { - "type": "object", - "description": "Options to configure the plugin" - }, - { - "type": "array", - "description": "Arguments to be provided to a plugin install function" - } - ], - "description": "The options to be passed to the plugin at install time. Can be either an object or an array. If an array is provided, each member will be treated as an argument to the plugin install function." - } - }, - "additionalProperties": true - } - }, - "type": "object", - "required": ["openmct"], - "properties": { - "openmct": { - "type": "object", - "oneOf": [ - { - "properties": { - "version": { - "type": "string", - "description": "Version of OpenMCT to use", - "default": "latest" - }, - "plugins": { - "type": "array", - "items": { - "oneOf": [ - { - "type": "string", - "description": "Plugin identifier (e.g., 'openmct.Plugins.LocalStorage')" - },{ - "$ref": "#/definitions/pluginMap" - } - ] - } - } - }, - "additionalProperties": false, - "required": ["version"] - }, - { - "properties": { - "npmPackage": { - "type": "string", - "description": "NPM package that provides Open MCT. If present this will override any Open MCT version specified." - }, - "plugins": { - "type": "array", - "items": { - "oneOf": [ - { - "type": "string", - "description": "Plugin identifier (e.g., 'openmct.Plugins.LocalStorage')" - },{ - "$ref": "#/definitions/pluginMap" - } - ] - } - } - }, - "additionalProperties": false, - "required": ["npmPackage"] - } - ] - } - } - } \ No newline at end of file diff --git a/instances/development/index.html b/instances/development/index.html deleted file mode 100644 index be60121..0000000 --- a/instances/development/index.html +++ /dev/null @@ -1,34 +0,0 @@ - - Open MCT - - - - - - - - - \ No newline at end of file diff --git a/instances/development/instance.yaml b/instances/development/instance.yaml deleted file mode 100644 index fc3011a..0000000 --- a/instances/development/instance.yaml +++ /dev/null @@ -1,124 +0,0 @@ -# yaml-language-server: $schema=assets/openmct-configuration-schema.json -openmct: - npmPackage: nasa/openmct#omm-r5.4.0-rc4 - plugins: - - mct-bootstrap-plugin: - npmPackage: file:./assets/mct-bootstrap-plugin - options: - timeSystem: utc - clock: local - startOffset: -60000 - endOffset: 0 - mode: realtime - - openmct.plugins.Espresso - - openmct.plugins.MyItems - - openmct.plugins.LocalStorage - - openmct.plugins.UTCTimeSystem - - openmct.plugins.PlanLayout: - options: - creatable: true - - openmct.plugins.DisplayLayout: - options: - showAsView: - - summary-widget - - vista.packetSummaryEvents - - vista.dataProducts - - vista.packets - - vista.frameSummary - - vista.frameWatch - - openmct.plugins.Conductor: - options: - menuOptions: - - name: Fixed - timeSystem: utc - bounds: - start: ${now} - ${thirty_minutes} - end: ${now} - presets: - - label: Last Day - bounds: - start: ${now} - ${one_day} - end: ${now} - - label: Last 2 hours - bounds: - start: ${now} - ${two_hours} - end: ${now} - - label: Last hour - bounds: - start: ${now} - ${one_hour} - end: ${now} - records: 10 - - name: Realtime - timeSystem: utc - clock: local - clockOffsets: - start: '-${thirty_minutes}' - end: ${thirty_seconds} - presets: - - label: 1 Hour - bounds: - start: '-${one_hour}' - end: ${thirty_seconds} - - label: 30 Minutes - bounds: - start: '-${thirty_minutes}' - end: ${thirty_seconds} - - label: 15 Minutes - bounds: - start: '-${fifteen_minutes}' - end: ${thirty_seconds} - - label: 5 Minutes - bounds: - start: '-${five_minutes}' - end: ${thirty_seconds} - - label: 1 Minute - bounds: - start: '-${one_minute}' - end: ${thirty_seconds} - - openmct.plugins.Snow - - openmct.plugins.ObjectMigration - - openmct.plugins.ClearData: - options: - - - table - - telemetry.plot.overlay - - telemetry.plot.stacked - - vista.packetSummaryEvents - - vista.dataProducts - - vista.packets - - vista.frameSummary - - vista.frameWatch - - vista.chanTableGroup - - indicator: false - - openmct.plugins.Filters: - options: - - - vista.alarmsView - - telemetry.plot.overlay - - table - - vista.chanTableGroup - - vista.commandEventsView - - vista.messagesView - - vista.evrView - - openmct.plugins.Notebook - - openmct.plugins.Clock: - options: - useClockIndicator: false - - openmct.plugins.DefaultRootName: - options: - - VISTA - - openmct-mcws-plugin: - npmPackage: file:/Users/jjviglio/OpenMCT/OMM/openmct-mcws/ - entryPoint: dist/openmct-mcws-plugin.js - options: - useDeveloperStorage: true - proxyUrl: http://localhost:8080/ - camUrl: '' - mcwsUrl: '' - namespaces: - - key: r50-dev - name: R5.0 Shared - url: '' - - userNamespace: true - key: r50-dev - name: R5.0 Users - url: '' - - openmct.plugins.example.Generator diff --git a/instances/development/package.json b/instances/development/package.json deleted file mode 100644 index ec6dd4a..0000000 --- a/instances/development/package.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "development", - "version": "1.0.0", - "description": "", - "main": "index.js", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "keywords": [], - "author": "", - "license": "ISC", - "devDependencies": { - "mct-bootstrap-plugin": "file:assets/mct-bootstrap-plugin", - "openmct": "github:nasa/openmct#omm-r5.4.0-rc4", - "openmct-mcws": "file:../..", - "openmct-mcws-plugin": "file:../.." - } -} From 905a423ec80d23debf842f537a5a8bbe94ae5848 Mon Sep 17 00:00:00 2001 From: Jamie V Date: Mon, 24 Nov 2025 12:00:03 -0800 Subject: [PATCH 05/22] updated readme --- README.md | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index cca6f10..fbdda2a 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,9 @@ -# Open MCT for MCWS -Open Mission Control Technologies for Mission Control Web Services (Open MCT for MCWS) is a next-generation web-based mission control framework for visualization of data on desktop and mobile devices. Open MCT for MCWS is built on the [Open MCT Framework](https://github.com/nasa/openmct), and includes adapter code for using MCWS as a telemetry and persistence provider. Open MCT is developed at NASA Ames Research Center in Silicon Valley, in collaboration with NASA AMMOS and the Jet Propulsion Laboratory, California Institute of Technology (under its contract with NASA, 80NM0018D0004). +# Open MCT for MCWS Plugin +Open Mission Control Technologies for Mission Control Web Services Plugin (Open MCT for MCWS) is used with Open MCT, a next-generation web-based mission control framework for visualization of data on desktop and mobile devices. Open MCT for MCWS Plugin is built for the [Open MCT Framework](https://github.com/nasa/openmct), and includes adapter code for using MCWS as a telemetry and persistence provider. Open MCT is developed at NASA Ames Research Center in Silicon Valley, in collaboration with NASA AMMOS and the Jet Propulsion Laboratory, California Institute of Technology (under its contract with NASA, 80NM0018D0004). ## Configuration -Various configurations and customizations are available by editing `default.yaml`. +Various configurations and customizations are available by editing `recipes/default.yaml`. +Development configurations and customizations are available by editing `recipes/development.yaml`. ### AMMOS configurations 1. `camUrl`: The url to the CAM server, if CAM is to be used for authentication. @@ -11,6 +12,9 @@ Various configurations and customizations are available by editing `default.yaml ## Development +### Prerequisite +You will need to install the [Open MCT Configurator](https://github.com/akhenry/openmct-configurator) + ### 1. Install Open MCT for MCWS In a terminal, run this command to install Open MCT for MCWS and its dependencies. This may take a few minutes. @@ -20,24 +24,19 @@ If you've installed Open MCT for MCWS locally before, first run this command. npm run clean -### 2. Modify config.js -Uncomment the `proxyUrl` setting in `config.js`. It is located under Developer -Settings near the end of the file. - -### 3. Run Open MCT for MCWS locally - - npm start +### 2. Modify development.yaml +If necessary, make any modifications to `development.yaml`, such as adding Open MCT core plugins or modifying settings for the Openmct for MCWS Plugin -With that running, browse to http://localhost:8080/ to access Open MCT for MCWS. +### 3. Build Open MCT with the Open MCT for MCWS Plugin locally using Open MCT Configurator -### 4. Rebuilding SASS stylesheets + npm run build:prod + mct build --recipe recipes/development.yaml --instance development + npm run serve - npm run build:prod - -With the stylesheets rebuilt, you can reload your browser (assuming the server is running) to see the rebuilt CSS. +With that running, browse to http://localhost:8080/ to access Open MCT with the Open MCT for MCWS Plugin ## Development MCWS server -To connect Open MCT for MCWS to MCWS, either run a local mock server, run MCWS locally, or connect to a remote instance of MCWS. +To connect Open MCT to MCWS, either run a local mock server, run MCWS locally, or connect to a remote instance of MCWS. ## Running a mock MCWS server An example mock mcws server - https://github.com/davetsay/mcws-test-server @@ -50,6 +49,7 @@ Refer to MCWS documentation. Running a development server requires that you are on the JPL network so that you can access a development MCWS server. You'll need to retrieve an authentication cookie and make a small modification to your Open MCT for MCWS configuration; here's how. + ### 1. Get your CAM cookie To get past CAM, you will need to export an environment variable, `COOKIE`, that contains your CAM authentication cookie. Instructions for @@ -72,7 +72,8 @@ Running the tests creates a code coverage report in `target/coverage`. ## Building for production npm install - mvn clean install + npm run build:prod + mct build --recipe recipes/default.yaml --instance default This will create a deployable artifact, `openmct_client.war` in the `target` directory. From cbe88409513c920a66e3724d2910cd912e9c0623 Mon Sep 17 00:00:00 2001 From: Jamie V Date: Mon, 24 Nov 2025 12:01:53 -0800 Subject: [PATCH 06/22] updated readme with todo --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index fbdda2a..2311523 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,7 @@ Running the tests creates a code coverage report in `target/coverage`. npm run build:prod mct build --recipe recipes/default.yaml --instance default +TODO: UPDATE THIS sentence, possibly with compressing the necessary files into a war. This will create a deployable artifact, `openmct_client.war` in the `target` directory. From 734847a5825aa133ae037068fd6f8d6806f8c5c5 Mon Sep 17 00:00:00 2001 From: Jamie V Date: Mon, 24 Nov 2025 12:03:24 -0800 Subject: [PATCH 07/22] update to version 1 of plugin, this is up for debate --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ed79ae6..0c8bee6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "openmct-mcws-plugin", - "version": "v5.4.0-rc3", + "version": "v1.0.0", "description": "Open MCT for MCWS", "main": "dist/openmct-mcws-plugin.js", "type": "module", From 4321d0433d740b1722d1ca9b37d1cf6ac612e096 Mon Sep 17 00:00:00 2001 From: Jamie V Date: Mon, 24 Nov 2025 12:06:00 -0800 Subject: [PATCH 08/22] update pom --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index d32d6a9..7360a8c 100644 --- a/pom.xml +++ b/pom.xml @@ -5,8 +5,8 @@ 4.0.0 gov.nasa.arc.wtd openmct-client - Open MCT for MCWS Client - v5.4.0-rc3 + Open MCT for MCWS Plugin + v1.0.0 war From f9b9fabab8b5d6ca13e826cb48d383df12add0a8 Mon Sep 17 00:00:00 2001 From: Andrew Henry Date: Wed, 26 Nov 2025 13:05:38 -0800 Subject: [PATCH 09/22] Remove handling for local proxy --- src/services/mcws/MCWSClient.js | 36 ++++++--------------------------- 1 file changed, 6 insertions(+), 30 deletions(-) diff --git a/src/services/mcws/MCWSClient.js b/src/services/mcws/MCWSClient.js index ee8448c..fb0edba 100644 --- a/src/services/mcws/MCWSClient.js +++ b/src/services/mcws/MCWSClient.js @@ -18,33 +18,17 @@ class MCWSClient { if (this.config.proxyUrl) { const params = options.params; - let isJsonResponse = false; if (params && Object.keys(params).length > 0) { - // Check if this is a JSON response before we delete params - if (params.output === 'json') { - isJsonResponse = true; - } - const paramKeys = Object.keys(params); const formattedParams = paramKeys .map((key) => `${key}=${encodeURIComponent(params[key])}`) .join('&'); url += `?${formattedParams}`; - - // Delete params after using them to prevent baseRequest from adding them again - delete options.params; } url = `${this.config.proxyUrl}proxyUrl?url=${encodeURIComponent(url)}`; - - // Preserve the isJsonResponse flag for baseRequest - if (isJsonResponse) { - options.isJsonResponse = true; - } - - delete options.params; } options.url = url; @@ -60,16 +44,13 @@ class MCWSClient { async baseRequest(url, options) { let response; let isJsonResponse = false; - - if (options?.isJsonResponse) { - isJsonResponse = true; - delete options.isJsonResponse; - } - - this.pending++; if (options?.params) { + if (options.params?.output === 'json') { + isJsonResponse = true; + } + const params = new URLSearchParams(options.params); // append options params to url @@ -85,13 +66,8 @@ class MCWSClient { try { response = await fetch(url, options); } catch (error) { - if (error.name === 'AbortError') { - console.warn('Request aborted', error); - return; - } else { - console.error('Error in base request', error); - throw error; - } + console.error('Error in base request', error); + throw error; } finally { this._updatePending(); } From 262a2bb1daa636752f917902c9b970a823946512 Mon Sep 17 00:00:00 2001 From: Andrew Henry Date: Wed, 26 Nov 2025 13:05:56 -0800 Subject: [PATCH 10/22] Remove local dev server --- serve-instance.js | 173 ---------------------------------------------- 1 file changed, 173 deletions(-) delete mode 100755 serve-instance.js diff --git a/serve-instance.js b/serve-instance.js deleted file mode 100755 index fe9eaa4..0000000 --- a/serve-instance.js +++ /dev/null @@ -1,173 +0,0 @@ -#!/usr/bin/env node - -/** - * Simple server to serve OpenMCT instances with proxy support - * Replicates webpack-dev-server proxy configuration - * - * Usage: node serve-instance.js [instance-name] [port] - * Example: node serve-instance.js development 8080 - */ - -import express from 'express'; -import { createProxyMiddleware } from 'http-proxy-middleware'; -import path from 'path'; -import { fileURLToPath } from 'url'; -import { dirname } from 'path'; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -const instanceName = process.argv[2] || 'development'; -const port = parseInt(process.argv[3] || '8080', 10); -const instancePath = path.join(__dirname, 'instances', instanceName); - -// Proxy configuration (matching webpack.dev.js) -const proxyUrl = process.env.PROXY_URL || 'http://localhost:8080'; -const apiUrl = process.env.API_URL || ''; -const proxyHeaders = {}; - -if (process.env.COOKIE) { - proxyHeaders.Cookie = process.env.COOKIE; -} - -const app = express(); - -// Proxy: /mcws-test -> http://localhost:8090 -app.use('/mcws-test', createProxyMiddleware({ - target: 'http://localhost:8090', - changeOrigin: true, - secure: false, - logLevel: 'debug' -})); - -// Proxy: /mcws -> apiUrl -if (apiUrl) { - app.use('/mcws', createProxyMiddleware({ - target: apiUrl, - changeOrigin: true, - secure: false, - headers: proxyHeaders, - logLevel: 'debug' - })); -} - -// Proxy: /proxyUrl -> extract URL from query parameter and proxy to it -// This replicates webpack-dev-server behavior where pathRewrite returns the full URL -app.use('/proxyUrl', express.raw({ type: '*/*', limit: '50mb' }), async (req, res, next) => { - let targetUrl = null; - - // Extract the 'url' parameter from the query string - // We need to handle this carefully because the url parameter itself may contain query parameters - const rawQuery = req.originalUrl?.split('?')[1] || req.url?.split('?')[1]; - - if (rawQuery) { - // Find the 'url=' parameter - it should be first - // Extract everything from 'url=' until the first unencoded '&' (or end of string) - const urlMatch = rawQuery.match(/^url=(.+?)(?:&|$)/); - if (urlMatch) { - try { - const decodedUrl = decodeURIComponent(urlMatch[1]); - console.log('Decoded URL from query:', decodedUrl); - - // Handle relative URLs or query strings (like webpack dev server does) - if (decodedUrl.startsWith('http://') || decodedUrl.startsWith('https://')) { - targetUrl = decodedUrl; - } else if (decodedUrl.startsWith('?')) { - // Query string only - use request origin as base (like webpack dev server) - const origin = `${req.protocol}://${req.get('host')}`; - targetUrl = origin + decodedUrl; - } else if (decodedUrl.startsWith('/')) { - // Absolute path - use request origin as base - const origin = `${req.protocol}://${req.get('host')}`; - targetUrl = origin + decodedUrl; - } else { - // Relative path - use request origin + path - const origin = `${req.protocol}://${req.get('host')}`; - targetUrl = origin + '/' + decodedUrl; - } - } catch (e) { - // Invalid URL encoding - console.error('Error decoding URL:', e, 'Raw value:', urlMatch[1]); - targetUrl = null; - } - } - } - - if (!targetUrl) { - console.error('Failed to extract valid URL from request:', req.originalUrl || req.url); - return res.status(400).send('Missing or invalid url query parameter. The url parameter must be a valid HTTP/HTTPS URL.'); - } - - try { - console.log('Generic URL Proxy to:', targetUrl); - - // Prepare headers - exclude headers that shouldn't be forwarded - const headersToForward = { ...proxyHeaders }; - const headersToExclude = ['host', 'connection', 'content-length', 'transfer-encoding']; - for (const [key, value] of Object.entries(req.headers)) { - if (!headersToExclude.includes(key.toLowerCase())) { - headersToForward[key] = value; - } - } - - // Prepare fetch options - const fetchOptions = { - method: req.method, - headers: headersToForward - }; - - // Forward request body for non-GET/HEAD requests - if (req.method !== 'GET' && req.method !== 'HEAD' && req.body) { - fetchOptions.body = req.body; - } - - // Make a request to the target URL - const response = await fetch(targetUrl, fetchOptions); - - // Forward response headers - response.headers.forEach((value, key) => { - // Exclude headers that Express handles - if (key.toLowerCase() !== 'content-encoding') { - res.setHeader(key, value); - } - }); - - // Set status code - res.status(response.status); - - // Forward response body - const buffer = await response.arrayBuffer(); - res.send(Buffer.from(buffer)); - } catch (e) { - console.error('Proxy error:', e); - if (!res.headersSent) { - res.status(500).send('Proxy error: ' + e.message); - } - } -}); - -// Serve static files from instance directory -app.use(express.static(instancePath)); - -// Serve node_modules/openmct/dist if it exists -const openmctDistPath = path.join(instancePath, 'node_modules', 'openmct', 'dist'); -app.use('/node_modules/openmct/dist', express.static(openmctDistPath)); - -// Serve test_data directory (matching webpack dev server configuration) -const testDataPath = path.join(__dirname, 'test_data'); -app.use('/test_data', express.static(testDataPath)); - -app.listen(port, () => { - console.log(`Serving instance "${instanceName}" at http://localhost:${port}`); - console.log(`Instance path: ${instancePath}`); - console.log(`Proxy configuration:`); - console.log(` /mcws-test -> http://localhost:8090`); - if (apiUrl) { - console.log(` /mcws -> ${apiUrl}`); - } - console.log(` /proxyUrl -> ${proxyUrl} (with query.url rewriting)`); - if (Object.keys(proxyHeaders).length > 0) { - console.log(` Proxy headers:`, proxyHeaders); - } -}); - From 455e9084f7036d6b456b19446f29276315fe6357 Mon Sep 17 00:00:00 2001 From: Andrew Henry Date: Wed, 26 Nov 2025 13:06:31 -0800 Subject: [PATCH 11/22] Restore local dev server for development --- .webpack/webpack.dev.js | 102 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 .webpack/webpack.dev.js diff --git a/.webpack/webpack.dev.js b/.webpack/webpack.dev.js new file mode 100644 index 0000000..82aa5e3 --- /dev/null +++ b/.webpack/webpack.dev.js @@ -0,0 +1,102 @@ +/* +This configuration should be used for development purposes. It contains full source map, a +devServer (which be invoked using by `npm start`), and a non-minified Vue.js distribution. +*/ +const CopyWebpackPlugin = require('copy-webpack-plugin'); +const { merge } = require('webpack-merge'); +const common = require('./webpack.common'); +const path = require('path'); + +const proxyUrl = process.env.PROXY_URL || 'http://localhost:8080'; +const apiUrl = process.env.API_URL ?? ''; +const proxyHeaders = {}; +if (process.env.COOKIE) { + proxyHeaders.Cookie = process.env.COOKIE; +} + +module.exports = merge(common, { + mode: 'development', + entry: { + config: './config.js' + }, + plugins: [ + new CopyWebpackPlugin({ + patterns: [ + { + from: './index.html', + transform: function (content) { + // for dev, we serve out of dist/ so we need to replace any reference + return content.toString().replace(/"dist\//g, '"'); + } + }, + { from: './ExampleVenueDefinitions.json', to: 'ExampleVenueDefinitions.json' } + ] + }) + ], + watchOptions: { + // Since we use require.context, webpack is watching the entire directory. + // We need to exclude any files we don't want webpack to watch. + // See: https://webpack.js.org/configuration/watch/#watchoptions-exclude + ignored: [ + '**/{node_modules,dist,docs,e2e}', // All files in node_modules, dist, docs, e2e (leaving for future builds), + '**/{webpack*.js,babel*.js,package*.json}', // Config files + '**/*.{sh,md,png,ttf,woff,svg}', // Non source files + '**/.*' // dotfiles and dotfolders + ] + }, + devtool: 'eval-source-map', + devServer: { + devMiddleware: { + writeToDisk: (filePathString) => { + const filePath = path.parse(filePathString); + const shouldWrite = !filePath.base.includes('hot-update'); + + return shouldWrite; + } + }, + watchFiles: ['src/**/*.css'], + static: [ + { + directory: path.join(__dirname, '..', 'node_modules/openmct/dist'), + publicPath: '/node_modules/openmct/dist', + watch: false + }, + { + directory: path.join(__dirname, '..', 'test_data'), + publicPath: '/test_data', + watch: false + } + ], + client: { + progress: true, + overlay: false + }, + proxy: [ + { + context: ['/mcws-test'], + target: 'http://localhost:8090', + secure: false + }, + { + context: ['/mcws'], + target: apiUrl, + secure: false, + headers: proxyHeaders + }, + { + context: ['/proxyUrl'], + target: proxyUrl, + secure: false, + headers: proxyHeaders, + pathRewrite: (_path, req) => { + const apiUrl = req.query.url; + console.log('Generic URL Proxy to: ', apiUrl); + + return apiUrl; + } + } + ], + hot: true + }, + stats: 'errors-warnings' +}); From a3a14ae2cf05221c0bba80b621b1c1ecfb96616a Mon Sep 17 00:00:00 2001 From: Andrew Henry Date: Wed, 26 Nov 2025 13:07:59 -0800 Subject: [PATCH 12/22] Move recipes to build tool --- recipes/default.yaml | 52 ------------------------------------- recipes/development.yaml | 56 ---------------------------------------- 2 files changed, 108 deletions(-) delete mode 100644 recipes/default.yaml delete mode 100644 recipes/development.yaml diff --git a/recipes/default.yaml b/recipes/default.yaml deleted file mode 100644 index ee9a0c8..0000000 --- a/recipes/default.yaml +++ /dev/null @@ -1,52 +0,0 @@ -# yaml-language-server: $schema=../src/assets/openmct-configuration-schema.json -openmct: - version: '4.1.0' - plugins: - - openmct.plugins.Snow # Theme: 'Snow', 'Espresso' or 'Maelstrom' - - openmct.plugins.ObjectMigration - - openmct.plugins.ClearData: - options: - - ['table', 'telemetry.plot.overlay', 'telemetry.plot.stacked', 'vista.packetSummaryEvents', 'vista.dataProducts', 'vista.packets', 'vista.frameSummary', 'vista.frameWatch', 'vista.chanTableGroup'] - - indicator: false - - openmct.plugins.DisplayLayout: - options: - showAsView: - - summary-widget - - vista.packetSummaryEvents - - vista.dataProducts - - vista.packets - - vista.frameSummary - - vista.frameWatch - - openmct.plugins.Filters: - options: - - - vista.alarmsView - - telemetry.plot.overlay - - table - - vista.chanTableGroup - - vista.commandEventsView - - vista.messagesView - - vista.evrView - - openmct.plugins.UTCTimeSystem - - openmct.plugins.Notebook - - openmct.plugins.Clock: - options: - useClockIndicator: false - - openmct.plugins.DefaultRootName: - options: ['VISTA'] - # Open MCT for MCWS Plugin - - openmct-mcws-plugin: - options: - camUrl: '' - mcwsUrl: '' - namespaces: - - key: 'r50-dev' - name: 'R5.0 Shared' - url: '' - - userNamespace: true - key: 'r50-dev' - name: 'R5.0 Users' - url: '' - # Optional Dev Plugins - # - openmct.plugins.LocalStorage - # - openmct.plugins.MyItems - # Open MCT for MCWS Plugin Options \ No newline at end of file diff --git a/recipes/development.yaml b/recipes/development.yaml deleted file mode 100644 index e172401..0000000 --- a/recipes/development.yaml +++ /dev/null @@ -1,56 +0,0 @@ -# yaml-language-server: $schema=../src/assets/openmct-configuration-schema.json -openmct: - npmPackage: nasa/openmct#omm-r5.4.0-rc4 - plugins: - - openmct.plugins.Snow # Theme: 'Snow', 'Espresso' or 'Maelstrom' - - openmct.plugins.ObjectMigration - - openmct.plugins.ClearData: - options: - - ['table', 'telemetry.plot.overlay', 'telemetry.plot.stacked', 'vista.packetSummaryEvents', 'vista.dataProducts', 'vista.packets', 'vista.frameSummary', 'vista.frameWatch', 'vista.chanTableGroup'] - - indicator: false - - openmct.plugins.DisplayLayout: - options: - showAsView: - - summary-widget - - vista.packetSummaryEvents - - vista.dataProducts - - vista.packets - - vista.frameSummary - - vista.frameWatch - - openmct.plugins.Filters: - options: - - - vista.alarmsView - - telemetry.plot.overlay - - table - - vista.chanTableGroup - - vista.commandEventsView - - vista.messagesView - - vista.evrView - - openmct.plugins.UTCTimeSystem - - openmct.plugins.Notebook - - openmct.plugins.Clock: - options: - useClockIndicator: false - - openmct.plugins.DefaultRootName: - options: ['VISTA'] - # Open MCT for MCWS Plugin - - openmct-mcws-plugin: - npmPackage: file:/Users/jjviglio/OpenMCT/OMM/openmct-mcws/ - entryPoint: dist/openmct-mcws-plugin.js - options: - useDeveloperStorage: true - proxyUrl: 'http://localhost:8080/' - camUrl: '' - mcwsUrl: '' - namespaces: - - key: 'r50-dev' - name: 'R5.0 Shared' - url: '' - - userNamespace: true - key: 'r50-dev' - name: 'R5.0 Users' - url: '' - # Required core plugins for development - - openmct.plugins.LocalStorage - - openmct.plugins.MyItems - - openmct.plugins.example.Generator \ No newline at end of file From 6b5d1b67a005a2b36364971b1a36f69639c5c562 Mon Sep 17 00:00:00 2001 From: Jamie V Date: Wed, 3 Dec 2025 10:28:06 -0800 Subject: [PATCH 13/22] wip: pulling out providers and utilty functions --- src/historical/HistoricalProvider.js | 56 +--------------- .../providers/ChannelLadProvider.js | 64 +++++++++++++++++++ src/utils/utils.js | 26 ++++++++ 3 files changed, 91 insertions(+), 55 deletions(-) create mode 100644 src/historical/providers/ChannelLadProvider.js create mode 100644 src/utils/utils.js diff --git a/src/historical/HistoricalProvider.js b/src/historical/HistoricalProvider.js index c524cb9..6d8074e 100644 --- a/src/historical/HistoricalProvider.js +++ b/src/historical/HistoricalProvider.js @@ -4,6 +4,7 @@ import filterService from 'services/filtering/FilterService.js'; import types from '../types/types.js'; import UTCDayOfYearFormat from '../formats/UTCDayOfYearFormat.js'; import moment from 'moment'; +import channelLADProvider from './providers/ChannelLadProvider.js'; const UTC_FORMAT_KEY = window.openmctMCWSConfig?.time?.utcFormat; @@ -41,61 +42,6 @@ function debounce(func, wait = 0) { }; } -// CHANNEL LAD PROVIDER -const channelLADProvider = { - supportsRequest: function (domainObject, request) { - return ( - domainObject.type === types.Channel.key && - domainObject.telemetry && - (domainObject.telemetry.channelLADUrl || domainObject.telemetry.channelHistoricalUrl) && - (request.strategy === 'latest' || request.size === 1) - ); - }, - batchId: function (domainObject, options) { - return [domainObject.telemetry.channelLADUrl, options.domain, options.filters]; - }, - batchRequest: function (batch) { - const requests = Object.values(batch.requestsById); - const params = requests[0].params; - const options = requests[0].options; - - params.lad_type = params.sort; - params.select = - '(dn,eu,channel_id,ert,scet,sclk,lst,record_type,dn_alarm_state,eu_alarm_state,module,realtime,dss_id)'; - params.filter.channel_id__in = requests.map((req) => req.domainObject.telemetry.channel_id); - setSortFilter(params); - - const ladURL = requests[0].domainObject.telemetry.channelLADUrl; - const fallbackHistoricalURL = requests[0].domainObject.telemetry.channelHistoricalUrl; - let requestURL; - - if (!ladURL) { - requestURL = fallbackHistoricalURL; - delete params.lad_type; - } else { - requestURL = ladURL; - } - - mcws - .dataTable(requestURL, { signal: options.signal }) - .read(params) - .then((res) => { - const valuesByChannelId = groupBy(res, 'channel_id'); - const toFulfill = keyBy(requests, (req) => req.domainObject.telemetry.channel_id); - - Object.entries(valuesByChannelId).forEach(([id, values]) => { - toFulfill[id].resolve(values); - delete toFulfill[id]; - }); - Object.values(toFulfill).forEach((request) => { - request.resolve([]); - }); - }) - .catch((reason) => { - requests.forEach((request) => request.reject(reason)); - }); - } -}; // MINMAX PROVIDER const minMaxProvider = { diff --git a/src/historical/providers/ChannelLadProvider.js b/src/historical/providers/ChannelLadProvider.js new file mode 100644 index 0000000..d16b77a --- /dev/null +++ b/src/historical/providers/ChannelLadProvider.js @@ -0,0 +1,64 @@ +import mcws from 'services/mcws/mcws.js'; +import types from '../../types/types.js'; +import { groupBy, keyBy, setSortFilter } from '../../utils/utils.js'; + + + +class ChannelLadProvider { + supportsRequest(domainObject, request) { + return ( + domainObject.type === types.Channel.key && + domainObject.telemetry && + (domainObject.telemetry.channelLADUrl || domainObject.telemetry.channelHistoricalUrl) && + (request.strategy === 'latest' || request.size === 1) + ); + } + + batchId(domainObject, options) { + return [domainObject.telemetry.channelLADUrl, options.domain, options.filters]; + } + + batchRequest(batch) { + const requests = Object.values(batch.requestsById); + const params = requests[0].params; + const options = requests[0].options; + + params.lad_type = params.sort; + params.select = + '(dn,eu,channel_id,ert,scet,sclk,lst,record_type,dn_alarm_state,eu_alarm_state,module,realtime,dss_id)'; + params.filter.channel_id__in = requests.map((req) => req.domainObject.telemetry.channel_id); + setSortFilter(params); + + const ladURL = requests[0].domainObject.telemetry.channelLADUrl; + const fallbackHistoricalURL = requests[0].domainObject.telemetry.channelHistoricalUrl; + let requestURL; + + if (!ladURL) { + requestURL = fallbackHistoricalURL; + delete params.lad_type; + } else { + requestURL = ladURL; + } + + mcws + .dataTable(requestURL, { signal: options.signal }) + .read(params) + .then((res) => { + const valuesByChannelId = groupBy(res, 'channel_id'); + const toFulfill = keyBy(requests, (req) => req.domainObject.telemetry.channel_id); + + Object.entries(valuesByChannelId).forEach(([id, values]) => { + toFulfill[id].resolve(values); + delete toFulfill[id]; + }); + Object.values(toFulfill).forEach((request) => { + request.resolve([]); + }); + }) + .catch((reason) => { + requests.forEach((request) => request.reject(reason)); + }); + } +} + +export default new ChannelLadProvider(); diff --git a/src/utils/utils.js b/src/utils/utils.js new file mode 100644 index 0000000..2f7f218 --- /dev/null +++ b/src/utils/utils.js @@ -0,0 +1,26 @@ +// Helper function to replace lodash groupBy +export function groupBy(array, key) { + return array.reduce((result, item) => { + const group = item[key]; + if (!result[group]) { + result[group] = []; + } + result[group].push(item); + return result; + }, {}); +} + +// Helper function to replace lodash keyBy +export function keyBy(array, key) { + return array.reduce((result, item) => { + const groupKey = typeof key === 'function' ? key(item) : item[key]; + result[groupKey] = item; + return result; + }, {}); +} + +export function setSortFilter(params) { + if (window.openmctMCWSConfig?.disableSortParam === true) { + delete params.sort; + } +} \ No newline at end of file From 4ccc6378c5c285828c7dcb4879c570b3a3361ec8 Mon Sep 17 00:00:00 2001 From: Andrew Henry Date: Wed, 3 Dec 2025 11:15:01 -0800 Subject: [PATCH 14/22] Restored legacy build process --- .webpack/webpack.dev.js | 23 +- config.js | 614 ++++++++++++++++++++++++++++++++++++++++ index.html | 37 +++ legacy-index.js | 91 ++++++ package.json | 9 +- plugin.js | 1 - 6 files changed, 762 insertions(+), 13 deletions(-) create mode 100644 config.js create mode 100644 index.html create mode 100644 legacy-index.js diff --git a/.webpack/webpack.dev.js b/.webpack/webpack.dev.js index 82aa5e3..1bfce10 100644 --- a/.webpack/webpack.dev.js +++ b/.webpack/webpack.dev.js @@ -2,10 +2,13 @@ This configuration should be used for development purposes. It contains full source map, a devServer (which be invoked using by `npm start`), and a non-minified Vue.js distribution. */ -const CopyWebpackPlugin = require('copy-webpack-plugin'); -const { merge } = require('webpack-merge'); -const common = require('./webpack.common'); -const path = require('path'); +import CopyWebpackPlugin from 'copy-webpack-plugin'; +import { merge } from 'webpack-merge'; +import common from './webpack.common.js'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import { dirname, join } from 'path'; +const __dirname = dirname(fileURLToPath(import.meta.url)); const proxyUrl = process.env.PROXY_URL || 'http://localhost:8080'; const apiUrl = process.env.API_URL ?? ''; @@ -14,10 +17,10 @@ if (process.env.COOKIE) { proxyHeaders.Cookie = process.env.COOKIE; } -module.exports = merge(common, { +export default merge(common, { mode: 'development', entry: { - config: './config.js' + 'legacy-index': './legacy-index.js' }, plugins: [ new CopyWebpackPlugin({ @@ -29,7 +32,8 @@ module.exports = merge(common, { return content.toString().replace(/"dist\//g, '"'); } }, - { from: './ExampleVenueDefinitions.json', to: 'ExampleVenueDefinitions.json' } + { from: './ExampleVenueDefinitions.json', to: 'ExampleVenueDefinitions.json' }, + { from: './config.js', to: 'config.js' } ] }) ], @@ -61,6 +65,11 @@ module.exports = merge(common, { publicPath: '/node_modules/openmct/dist', watch: false }, + { + directory: path.join(__dirname, '..', 'dist'), + publicPath: '/node_modules/openmct-mcws-plugin/dist', + watch: false + }, { directory: path.join(__dirname, '..', 'test_data'), publicPath: '/test_data', diff --git a/config.js b/config.js new file mode 100644 index 0000000..bb73a1c --- /dev/null +++ b/config.js @@ -0,0 +1,614 @@ +(function () { + const openmctMCWSConfig = { + /** + * camUrl: url to the CAM server this instance uses for auth. + * ***** REQUIRED ***** + */ + camUrl: '', + /** + * mcwsUrl: url for MCWS root. + * ***** REQUIRED ***** + */ + mcwsUrl: '', + /** + * theme: either 'Snow', 'Espresso' or 'Maelstrom' + */ + theme: 'Snow', + + /** + * Namespaces: each entry below adds a root folder. + * + * Namespace Properties: + * * key: string, unique key for this namespace. + * * name: string, user-visible name for this namespace. + * * url: string, URL to MCWS namespace which will store the contents of + * the namespace + * * userNamespace: boolean, optional, defaults to false. If true, this + * namespace will be used to create per-user folders. + * + * ***** URL REQUIRED ***** + */ + namespaces: [ + { + key: 'r50-dev', + name: 'R5.0 Shared', + url: '' + }, + { + userNamespace: true, + key: 'r50-dev', + name: 'R5.0 Users', + url: '' + } + ], + + /** + * venueAware - options here enable venue aware mode and allow + * configuration of venue aware mode. Added in R4.0. + * + * Venue aware configuration allows pre-configuration with a + * list of venues and datasets such that users are prompted to select + * either an active venue or a historical session that they'd like to + * review. + * + * Enabling venue-aware mode disables manual creation of datasets. + */ + venueAware: { + /** + * enabled: {true, false} + */ + enabled: false, + + /** + * venues: either a list of venue definitions or a url for a JSON + * venue definition file. + * If a url is provided, it will be queried at run time + * to determine the venues available. + * + * An example of a JSON venue definition file is provided in + * "ExampleVenueDefinitions.json". + */ + venues: 'ExampleVenueDefinitions.json' + }, + + /** + * Taxonomy: options here effect how various telemetry types are + * displayed. + */ + taxonomy: { + /** + * evrDefaultBackgroundColor: default background color for EVRs. + * Set to `undefined` to use the theme default. Otherwise, specify + * a hex string for an RGB color, e.g. `#ababab`. + */ + evrDefaultBackgroundColor: undefined, + + /** + * evrDefaultForegroundColor: default foreground color for EVRs. + * Set to `undefined` to use the theme default. Otherwise, specify + * a hex string for an RGB color, e.g. `#ababab`. + */ + evrDefaultForegroundColor: undefined, + + /** + * evrBackgroundColorByLevel: specify the background color of EVRs + * by level. If a level is not defined here, it will use the + * default specified above. Keys are specific EVR levels, and values + * must be a a hex string for an RGB color, e.g. `#ababab`. + */ + + evrBackgroundColorByLevel: { + /** FSW Specific */ + FATAL: '#ff0000', + WARNING_HI: '#ff7f24', + WARNING_LO: '#ffff00', + COMMAND: '#00bfff', + ACTIVITY_HI: '#6d6d6d', + ACTIVITY_LO: '#dcdcdc', + DIAGNOSTIC: '#00ff00', + EVR_UNKNOWN: '#00ff00', + + /** SSE Specific */ + FAULT: '#ff0000', + WARNING: '#ff7f24' + }, + + /** + * evrForegroundColorByLevel: specify the foreground color of EVRs + * by level. If a level is not defined here, it will use the + * default specified above. Keys are specific EVR levels, and values + * must be a a hex string for an RGB color, e.g. `#ababab`. + */ + + evrForegroundColorByLevel: { + /** FSW Specific */ + FATAL: '#ffffff', + WARNING_HI: '#000000', + WARNING_LO: '#000000', + COMMAND: '#ffffff', + ACTIVITY_HI: '#ffffff', + ACTIVITY_LO: '#000000', + DIAGNOSTIC: '#000000', + EVR_UNKNOWN: '#000000', + + /** SSE Specific */ + FAULT: '#ffffff', + WARNING: '#000000' + } + }, + + /** + * Settings for time APIs and formats. + */ + time: { + /** + * Default conductor mode. Available options: + * + * * 'fixed' : fixed time bounds. + * * 'utc.local' : follow local utc clock. Only available when + * allowRealtime is true and scet or ert timeSystems + * are available. + * * 'scet.lad' : follow latest scet seen in telemetry data. Only + * available when allowLAD is true and scet + * timeSystem is enabled. + * * 'ert.lad' : follow latest ert seen in telemetry data. Only + * available when allowLAD is true and ert + * timeSystem is enabled. + * * 'sclk.lad' : follow latest sclk seen in telemetry data. Only + * available when allowLAD is true and sclk + * timeSystem is enabled. + * * 'msl.sol.lad' : follow latest mslsol seen in telemetry data. Only + * available when allowLAD is true and mslsol + * timeSystem is enabled. + */ + defaultMode: 'fixed', + + /** + * utcFormat: available options + * + * * 'utc.day-of-year': 2015-015T12:34:56.999 + * * 'utc' : 2015-01-15T12:34:56.999 + */ + utcFormat: 'utc.day-of-year', + + /** + * optional + * + * lmstEpoch: Epoch date for LMST Time System + * + * It has to be a Date.UTC instance as follows: + * lmstEpoch: Date.UTC(2020, 2, 18, 0, 0, 0) + */ + lmstEpoch: Date.UTC(2020, 2, 18, 0, 0, 0), + + /* + * subscriptionMCWSFilterDelay: delay in milliseconds for combining filters for the same subscription + * endpoint connection. Smaller value = quicker display of realtime data (ex, 10ms in a + * low latency environment), higher value = avoids potentially creating and subsequently tearing down new websocket connections if filter changes are happening faster than server response times + * (ex, 100ms+ in a high latency environment) + */ + subscriptionMCWSFilterDelay: 100, + + /** + * timeSystems: specify the time systems to use. + * Options are 'scet', 'ert', 'sclk', 'msl.sol' and 'lmst'. + */ + timeSystems: ['scet', 'ert'], + + /** + * timeSystems advanced configuration: + * Replace the above basic configuration with timeSystem specific configurations + * + * key property is required and other options are optional + * timeSystem: + * * key: string, required. Time system. Options are 'scet', 'ert', 'sclk', 'msl.sol' and 'lmst'. + * * limit: number, optional - maximum duration between start and end bounds allow + * * modeSettings: object, optional - presets for convenience. + * * * fixed: object, optional - valid objects are bounds objects and presets array. + * * * realtime: object, optional - valid objects are clockOffsets and presets array. + * * * lad:object, optional - valid objects are clockOffsets. + * * * * + * * * * Optional objects: + * * * * bounds: start and end bounds for preset as numbers + * * * * * * * * start: and end: can be declared as a number or a function returning a number + * * * * presets: array of objects consisting of: + * * * * * bounds: - required. + * * * * * label: - required, string + * * * * clockOffsets: object, optional. Start and end relative to active clock. + * * * * start: and end: numbers relative to active clock's 0. Start is negative, end is positive. + * *advanced** example configuration below + * + timeSystems: [ + { + key:'scet', + modeSettings:{ + fixed:{ + bounds:{ + // 1 day ago + start: new Date( + Date.UTC( + new Date().getUTCFullYear(), + new Date().getUTCMonth(), + new Date().getUTCDate() + ) - 1 * 864e5 + ).getTime(), + end: new Date( + Date.UTC( + new Date().getUTCFullYear(), + new Date().getUTCMonth(), + new Date().getUTCDate() + ) + 864e5 - 1 + ).getTime() + }, + presets:[ + { + label: 'Last 2 hours (SCET Recorded)', + bounds: { + start: () => Date.now() - 1000 * 60 * 60 * 2, + end: () => Date.now() + } + }, + ] + }, + realtime:{ + clockOffsets:{ + start: -60 * 60 * 1000, + end: 5 * 60 * 1000 + }, + presets:[ + { + label: 'Last 2 hours (SCET Realtime)', + bounds: { + start: -60 * 60 * 1000 * 2, + end: 5 * 60 * 1000 + } + } + ] + }, + lad:{ + clockOffsets:{ + start: -60 * 60 * 1000, + end: 5 * 60 * 1000 + }, + }, + }, + limit: 1000 * 60 * 60 * 60 + }, + { + key:'ert', + modeSettings:{ + fixed:{ + bounds:{ + // 1 day ago + start: new Date( + Date.UTC( + new Date().getUTCFullYear(), + new Date().getUTCMonth(), + new Date().getUTCDate() + ) - 1 * 864e5 + ).getTime(), + // today + end: new Date( + Date.UTC( + new Date().getUTCFullYear(), + new Date().getUTCMonth(), + new Date().getUTCDate() + ) + 864e5 - 1 + ).getTime() + }, + presets:[ + { + label: 'Last 2 hours (ERT Recorded)', + bounds: { + start: Date.now() - 1000 * 60 * 60 * 2, + end: Date.now() + } + }, + ] + }, + realtime:{ + clockOffsets:{ + start: -60 * 60 * 1000, + end: 5 * 60 * 1000 + }, + presets:[ + { + label: 'Last 2 hours (ERT Realtime)', + bounds: { + start: -60 * 60 * 1000 * 2, + end: 5 * 60 * 1000 + } + } + ] + }, + lad:{ + clockOffsets:{ + start: -60 * 60 * 1000, + end: 5 * 60 * 1000 + }, + }, + }, + limit: 1000 * 60 * 60 * 60 + } + ], + */ + + /** + * allowRealtime: whether or not to allow utc-relative time conductor. + */ + allowRealtime: true, + /** + * allowLAD: whether or not to allow latest data relative time conductor. + * + * Note: allowRealtime must be true to use this option + */ + allowLAD: true, + /** + * records: number of previous bounds per timeSystem to save in time conductor history. + */ + records: 10 + }, + + /** + * Plugin Support + * Example configuration: + * plugins: { + // Simple enable: + // openmct.plugins.anotherPlugin() + anotherPlugin: { + enabled: true + }, + // Enable with options: + // openmct.plugins.ConfiguredPlugin({setting1: 'value1', setting2: 'value2'}, 1000) + // 1000 is passed as the second argument to the plugin constructor + ConfiguredPlugin: { + enabled: true, + // these are passed as arguments to the plugin constructor + configuration: [ + { + setting1: 'value1', + setting2: 'value2' + }, + 1000 + ] + } + } + */ + plugins: { + /** + * Enable/disable summary widgets. Added in R3.4.0. + */ + summaryWidgets: { + enabled: true + }, + BarChart: { + enabled: false + }, + ScatterPlot: { + enabled: false + }, + Timeline: { + enabled: false + }, + Timelist: { + enabled: false + }, + PlanLayout: { + enabled: false, + configuration: [ + { + name: 'Gantt Chart', + creatable: true, + namespace: '' // namespace to use for the activity state object + } + ] + } + }, + + /** + * maxResults: a maximum results limit for historical queries + */ + // maxResults: 10000, + + /** + * sessionHistoricalMaxResults: a maximum results limit for historical session queries + */ + sessionHistoricalMaxResults: 100, + + /** + * batchHistoricalChannelQueries: default false + * set to true to batch channel historical queries in telemetry tables + * + * USE WITH CAUTION + * You can more easily overwhelm the backend with a larger single query + */ + batchHistoricalChannelQueries: false, + + /** + * enable to not send sort param in historical queries + * only set this configuration to true if you are certain you wish to disable backend sort + */ + disableSortParam: false, + + /** + * Url used to listen to message stream for StartOfSession and + * EndOfSession messages + */ + messageStreamUrl: '', + + /** + * Use to set mission specific filters on messages by message type + * + * Filter Object Properties: + * * value: string, message type code value + * * label: string, user-visible label for identifying this filter option + */ + messageTypeFilters: [ + /** + * {value: 'LossOfSync', label: 'Loss of Sync'}, + * ... + * {value: 'InSync', label: 'In Sync'} + */ + ], + + /** + * Use to set up expected VCID's in the frame event stream. + * Frame Accountability View will highlight the unexpected VC's in orange. + */ + frameAccountabilityExpectedVcidList: [ + /** + * 234223, + * 234234, + * 223423 + */ + ], + + /** + * Use to warn the user and block historical query when the ert, scet or lmst based time-conductor timespan exceeds set limits + * units - milliseconds + * !!when set to undefined, user will not be warned and queries will not be blocked + */ + queryTimespanLimit: undefined, + + /** + * Time since last received realtime datum. + * Any datum that is received after the set timespan will have a stale (isStale) property set. + * units - milliseconds + * !!when set to undefined, there will be no global staleness timespan set. + */ + globalStalenessInterval: undefined, + + /** + * Register custom formatters for use in Telemetry View in Display Layout's + * Custom Formatters need to be an object with a unique String "key" property + * and a "format" function that accepts a value and returns formatted value + * + * Custom formatters can be accessed in Display Layout's format inspector view, + * with a pre-pended '&', e.g the 'hello-world' formatter can be accessed by '&hello-world' + */ + customFormatters: [ + /** + * { + key: 'hello-world', + format: (value) => { + return `hello-world: ${value}`; + } + } + */ + ], + + /** + * Use to set deployment specific session configuration + * + * historicalSessionFilter Object Properties: + * * disable: Boolean, to disable historical session filtering + * * maxRecords: Number, a number greater than 0, for maximum historical session records to be returned + * + * realtimeSession Object Properties: + * * disable: Boolean, to disable realtime sessions. Note - this will disable all websocket connections + */ + sessions: { + historicalSessionFilter: { + disable: false, + maxRecords: 100, + denyUnfilteredQueries: false + }, + realtimeSession: { + disable: false + } + }, + + /** + * Enable global filters for ALL telemetry requests that support the filter. + * Telemetry filters modify the 'filter' field in queries to MCWS. + * + * key property is required and other options are optional + * globalFilters: array, optional - list of global filters to configure. + * * key: string, required. Filter column, e.g. vcid + * * name: string, required. Identifier of the filter in the selection window. + * * icon: 'icon-flag', string, icon. Not implemented - potentially icon for minimized filter list. + * * filter: object, required. Filter object to implement + * * * comparator: string, required. currently supports 'equals' + * * * singleSelectionThreshold: boolean, required. currently supports true only. + * * * defaultLabel: string, optional. Defaults to 'None'. Label to show if filter inactive. + * * * possibleValues: array, required. List of values and labels for filter. + * * * * label: string, required. Label to show in filter selection dropdown. + * * * * value: string, required. value to set parameter to in filtered query. + * How to use: + * The global filters will be available from the Global Filters indicator. + * Enable a filter by selecting the desired filter from the dropdown and hitting update. + * Outgoing requests that use the 'filter' parameter to MCWS will be modified with your filter. + * example below, selecting 'A side' will ensure that the filter parameter in mcws includes: + * vcid='1,2,3'. Note that poorly formatted filters may not pass MCWS API validation. + * + */ + /* + globalFilters: [ + { + name: 'VCID', + key: 'vcid', + icon: 'icon-flag', + filter: { + comparator: 'equals', + singleSelectionThreshold: true, + defaultLabel: "A & B", + possibleValues: [ + { + label: 'A Side', + value: '1,2,3' + }, + { + label: 'B Side', + value: '4,5,6' + } + ] + } + }, + { + name: 'Realtime', + key: 'realtime', + filter: { + comparator: 'equals', + singleSelectionThreshold: true, + defaultLabel: "REC & RLT", + possibleValues: [ + { + label: 'Realtime', + value: true + }, + { + label: 'Recorded', + value: false + } + ] + } + } + ], + */ + /** + * Table Performance Mode Configuration + * Can increase performance by limiting the maximum rows retained and displayed by tables + * Affects all bounded table types such as Telemetry and EVR tables + * Does not affect latest available tables such as Channel tables + * @typedef TablePerformanceOptions + * @type {object} + * @property {('performance'|'unlimited')} telemetryMode performance mode limits the maximum table rows + * @property {Boolean} persistModeChange whether changes in the UI are persisted with the table + * @property {Number} rowLimit the maximum number of rows in performance mode + */ + tablePerformanceOptions: { + telemetryMode: 'unlimited', + persistModeChange: false, + rowLimit: 50 + }, + /** + * Developer Settings-- do not modify these unless you know what + * they do! + */ + proxyUrl: 'http://localhost:8080/', + useDeveloperStorage: true, + assetPath: 'node_modules/openmct/dist' + }; + + window.openmctMCWSConfig = openmctMCWSConfig; +})(); diff --git a/index.html b/index.html new file mode 100644 index 0000000..e1c36da --- /dev/null +++ b/index.html @@ -0,0 +1,37 @@ + + + + + + + + + + + + + +
+ + diff --git a/legacy-index.js b/legacy-index.js new file mode 100644 index 0000000..5641424 --- /dev/null +++ b/legacy-index.js @@ -0,0 +1,91 @@ +import openmctMCWSPlugin from "./plugin.js"; +import openmct from "openmct"; + +document.addEventListener('DOMContentLoaded', async () => { + const config = window.openmctMCWSConfig; + openmct.setAssetPath(config.assetPath || 'node_modules/openmct/dist'); + + //Optional Themes + if (config.theme) { + openmct.install(openmct.plugins[config.theme]()); + } else { + openmct.install(openmct.plugins.Snow()); + } + + if (config.useDeveloperStorage) { + openmct.install(openmct.plugins.LocalStorage()); + openmct.install(openmct.plugins.MyItems()); + } + + openmct.install( + openmct.plugins.Filters([ + 'vista.alarmsView', + 'telemetry.plot.overlay', + 'table', + 'vista.chanTableGroup', + 'vista.commandEventsView', + 'vista.messagesView', + 'vista.evrView' + ]) + ); + openmct.install(openmct.plugins.ObjectMigration()); + openmct.install( + openmct.plugins.DisplayLayout({ + showAsView: [ + 'summary-widget', + 'vista.packetSummaryEvents', + 'vista.dataProducts', + 'vista.packets', + 'vista.frameSummary', + 'vista.frameWatch' + ] + }) + ); + openmct.install( + openmct.plugins.ClearData( + [ + 'table', + 'telemetry.plot.overlay', + 'telemetry.plot.stacked', + 'vista.packetSummaryEvents', + 'vista.dataProducts', + 'vista.packets', + 'vista.frameSummary', + 'vista.frameWatch', + 'vista.chanTableGroup' + ], + { + indicator: false + } + ) + ); + openmct.install(openmct.plugins.UTCTimeSystem()); + openmct.install(openmct.plugins.Notebook()); + openmct.install(openmct.plugins.Clock({ useClockIndicator: false })); + + // install optional plugins, summary widget is handled separately as it was added long ago + if (config.plugins) { + if ( + config.plugins.summaryWidgets === true || + config.plugins.summaryWidgets?.enabled === true + ) { + openmct.install(openmct.plugins.SummaryWidget()); + } + + Object.entries(config.plugins).forEach(([plugin, pluginConfig]) => { + const pluginExists = openmct.plugins[plugin] || openmct.plugins.example[plugin]; + const pluginEnabled = pluginConfig?.enabled; + const isSummaryWidget = plugin === 'summaryWidgets'; + const installPlugin = pluginExists && pluginEnabled && !isSummaryWidget; + + if (installPlugin) { + openmct.install(openmct.plugins[plugin](...(pluginConfig.configuration ?? []))); + } else if (!pluginExists) { + console.warn(`Plugin ${plugin} does not exist. Check the plugin name and try again.`); + } + }); + } + + openmct.install(openmctMCWSPlugin(config)); + openmct.start(); +}, {once: true}); \ No newline at end of file diff --git a/package.json b/package.json index 0c8bee6..bc00bfd 100644 --- a/package.json +++ b/package.json @@ -21,10 +21,12 @@ "eslint-plugin-prettier": "5.2.3", "eslint-plugin-vue": "9.32.0", "eventemitter3": "5.0.1", + "express": "^4.18.2", "file-saver": "2.0.5", "git-rev-sync": "3.0.2", "globals": "15.14.0", "html2canvas": "1.4.1", + "http-proxy-middleware": "^2.0.6", "imports-loader": "0.8.0", "jasmine-core": "5.1.1", "jshint": "^2.7.0", @@ -54,11 +56,9 @@ "vue-eslint-parser": "^9.4.3", "vue-loader": "16.8.3", "webpack": "^5.94.0", - "webpack-cli": "5.1.1", + "webpack-cli": "^5.1.1", "webpack-dev-server": "5.0.2", - "webpack-merge": "5.10.0", - "express": "^4.18.2", - "http-proxy-middleware": "^2.0.6" + "webpack-merge": "5.10.0" }, "peerDependencies": { "openmct": "^4.1.0" @@ -74,7 +74,6 @@ "build:watch": "webpack --config ./.webpack/webpack.dev.js --watch", "test": "karma start --single-run", "jshint": "jshint src/**/*.js || exit 0", - "serve": "node serve-instance.js", "prepare": "node -e \"if (require('fs').existsSync('node_modules/webpack-cli')) { require('child_process').execSync('npm run build:prod', {stdio: 'inherit'}) }\" || true" }, "repository": { diff --git a/plugin.js b/plugin.js index d923537..df45df9 100644 --- a/plugin.js +++ b/plugin.js @@ -257,7 +257,6 @@ export default function openmctMCWSPlugin(options) { persistenceLoadedPromise.then(() => { window.openmctMCWSConfig = config; - // window.openmct = openmct; }); // load the css file From 4a91dae25195c51b8da3e3e8cd068488d9a34517 Mon Sep 17 00:00:00 2001 From: Andrew Henry Date: Wed, 3 Dec 2025 11:44:26 -0800 Subject: [PATCH 15/22] Support legacy hosting options --- .webpack/webpack.common.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.webpack/webpack.common.js b/.webpack/webpack.common.js index e70d0a4..551370c 100644 --- a/.webpack/webpack.common.js +++ b/.webpack/webpack.common.js @@ -23,7 +23,8 @@ try { const config = { context: join(__dirname, '..'), entry: { - 'openmct-mcws-plugin': './plugin.js' + 'openmct-mcws-plugin': './plugin.js', + 'legacy-index': './legacy-index.js' }, experiments: { outputModule: true, // Enables the feature From ec760eb38e53390597e3e5a16859cf2dc07f5070 Mon Sep 17 00:00:00 2001 From: Andrew Henry Date: Wed, 3 Dec 2025 13:39:33 -0800 Subject: [PATCH 16/22] Update peer dependencies --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index bc00bfd..f4082ab 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ "webpack-merge": "5.10.0" }, "peerDependencies": { - "openmct": "^4.1.0" + "openmct": "^4.1.0 || ^4.1.0-next" }, "scripts": { "clean": "npm cache clean --force;rm -rf ./dist ./node_modules ./target ./package-lock.json", From b8a5b5f37b354fd35b9a61f3466cd355e7d6bea3 Mon Sep 17 00:00:00 2001 From: Andrew Henry Date: Wed, 3 Dec 2025 13:55:57 -0800 Subject: [PATCH 17/22] Update file manifest for Maven --- pom.xml | 56 -------------------------------------------------------- 1 file changed, 56 deletions(-) diff --git a/pom.xml b/pom.xml index 7360a8c..0e9278a 100644 --- a/pom.xml +++ b/pom.xml @@ -19,9 +19,7 @@ - - org.apache.maven.plugins @@ -30,23 +28,11 @@ openmct_client - . index.html - openmct-mcws.js config.js - loader.js ExampleVenueDefinitions.json src/**/* dist/**/* @@ -86,48 +72,6 @@ - - - org.apache.tomcat.maven From 5fbb3bb1ddb2c4cf1aff366e6a611b707c9bf091 Mon Sep 17 00:00:00 2001 From: Jamie V Date: Wed, 3 Dec 2025 15:31:06 -0800 Subject: [PATCH 18/22] separating out providers to classes in their own files pulling out utility functions --- src/historical/HistoricalProvider.js | 358 +----------------- .../providers/ChannelAlarmProvider.js | 42 ++ .../providers/ChannelHistoricalProvider.js | 68 ++++ .../providers/CommandEventsProvider.js | 44 +++ .../providers/DataProductProvider.js | 39 ++ src/historical/providers/EvrProvider.js | 53 +++ .../HeaderChannelsHistoricalProvider.js | 25 ++ src/historical/providers/MinMaxProvider.js | 65 ++++ src/utils/utils.js | 36 +- 9 files changed, 377 insertions(+), 353 deletions(-) create mode 100644 src/historical/providers/ChannelAlarmProvider.js create mode 100644 src/historical/providers/ChannelHistoricalProvider.js create mode 100644 src/historical/providers/CommandEventsProvider.js create mode 100644 src/historical/providers/DataProductProvider.js create mode 100644 src/historical/providers/EvrProvider.js create mode 100644 src/historical/providers/HeaderChannelsHistoricalProvider.js create mode 100644 src/historical/providers/MinMaxProvider.js diff --git a/src/historical/HistoricalProvider.js b/src/historical/HistoricalProvider.js index 6d8074e..81ed3c3 100644 --- a/src/historical/HistoricalProvider.js +++ b/src/historical/HistoricalProvider.js @@ -1,342 +1,18 @@ -import mcws from 'services/mcws/mcws.js'; import sessionService from 'services/session/SessionService.js'; import filterService from 'services/filtering/FilterService.js'; -import types from '../types/types.js'; import UTCDayOfYearFormat from '../formats/UTCDayOfYearFormat.js'; import moment from 'moment'; import channelLADProvider from './providers/ChannelLadProvider.js'; +import minMaxProvider from './providers/MinMaxProvider.js'; +import evrProvider from './providers/EvrProvider.js'; +import dataProductProvider from './providers/DataProductProvider.js'; +import channelAlarmProvider from './providers/ChannelAlarmProvider.js'; +import commandEventsProvider from './providers/CommandEventsProvider.js'; +import headerChannelsHistoricalProvider from './providers/HeaderChannelsHistoricalProvider.js'; +import channelHistoricalProvider from './providers/ChannelHistoricalProvider.js'; const UTC_FORMAT_KEY = window.openmctMCWSConfig?.time?.utcFormat; -// Helper function to replace lodash groupBy -function groupBy(array, key) { - return array.reduce((result, item) => { - const group = item[key]; - if (!result[group]) { - result[group] = []; - } - result[group].push(item); - return result; - }, {}); -} - -// Helper function to replace lodash keyBy -function keyBy(array, key) { - return array.reduce((result, item) => { - const groupKey = typeof key === 'function' ? key(item) : item[key]; - result[groupKey] = item; - return result; - }, {}); -} - -// Helper function to replace lodash debounce -function debounce(func, wait = 0) { - let timeout; - return function executedFunction(...args) { - const later = () => { - clearTimeout(timeout); - func(...args); - }; - clearTimeout(timeout); - timeout = setTimeout(later, wait); - }; -} - - -// MINMAX PROVIDER -const minMaxProvider = { - supportsRequest: function (domainObject, request) { - return ( - domainObject.type === types.Channel.key && - domainObject.telemetry && - domainObject.telemetry.channelMinMaxUrl && - request.size > 1 && - request.strategy === 'minmax' - ); - }, - batchId: function (domainObject, options) { - return [ - domainObject.telemetry.channelLADUrl, - options.domain, - options.start, - options.end, - options.size - ]; - }, - batchRequest: function (batch) { - const requests = Object.values(batch.requestsById); - const params = requests[0].params; - const options = requests[0].options; - - params.minmax = - '(' + [requests[0].options.size, requests[0].options.domain, 'eu_or_dn'].join(',') + ')'; - params.select = - '(dn,eu,channel_id,ert,scet,sclk,lst,record_type,eu_or_dn,dn_alarm_state,eu_alarm_state)'; - params.filter.channel_id__in = requests.map((req) => req.domainObject.telemetry.channel_id); - setSortFilter(params); - - mcws - .dataTable(requests[0].domainObject.telemetry.channelMinMaxUrl, { - signal: options.signal - }) - .read(params) - .then((res) => { - const valuesByChannelId = groupBy(res, 'channel_id'); - const toFulfill = keyBy(requests, (req) => req.domainObject.telemetry.channel_id); - - Object.entries(valuesByChannelId).forEach(([id, values]) => { - toFulfill[id].resolve(values); - delete toFulfill[id]; - }); - Object.values(toFulfill).forEach((request) => { - request.resolve([]); - }); - }) - .catch((reason) => { - requests.forEach((request) => request.reject(reason)); - }); - }, - isMinMaxProvider: true -}; - -// EVR PROVIDER -const evrProvider = { - supportsRequest: function (domainObject, options) { - const hasTelemetry = Boolean(domainObject.telemetry); - const hasEvrHistoricalUrl = - hasTelemetry && Boolean(domainObject.telemetry.evrHistoricalUrl); - const hasEvrLADUrl = hasTelemetry && Boolean(domainObject.telemetry.evrLADUrl); - - return hasEvrHistoricalUrl || (hasEvrLADUrl && isLADQuery(options)); - }, - request: function (domainObject, options, params) { - const evrHistoricalUrl = domainObject.telemetry.evrHistoricalUrl; - const evrLADUrl = domainObject.telemetry.evrLADUrl; - - let url = evrHistoricalUrl; - - if (evrLADUrl && isLADQuery(options)) { - url = evrLADUrl; - params.lad_type = params.sort; - - /* - * For LAD queries by name, - * MCWS and AMPCS also requires a level - */ - if (domainObject.telemetry.evr_name) { - params.filter.level = '*'; - } - } - - if (domainObject.telemetry.level) { - params.filter.level = domainObject.telemetry.level; - } - - if (domainObject.telemetry.module) { - params.filter.module = domainObject.telemetry.module; - } - - if (domainObject.telemetry.evr_name) { - params.filter.name = domainObject.telemetry.evr_name; - } - - setMaxResults(domainObject, options, params); - - setSortFilter(params); - - return mcws.dataTable(url, { signal: options.signal }).read(params); - } -}; - -// DATA PRODUCT PROVIDER -const dataProductProvider = { - supportsRequest: function (domainObject, options) { - return domainObject.telemetry && !!domainObject.telemetry.dataProductUrl; - }, - request: function (domainObject, options, params) { - setMaxResults(domainObject, options, params); - setSortFilter(params); - - const promise = mcws - .dataTable(domainObject.telemetry.dataProductUrl, { signal: options.signal }) - .read(params); - - if (domainObject.type === 'vista.dataProducts') { - return promise.then((results) => { - results.forEach((datum) => { - const sessionId = datum.session_id; - if (datum.unique_name !== undefined) { - const uniqueName = datum.unique_name.replace(/\.dat$/, ''); - const filter = '(session_id=' + sessionId + ',unique_name=' + uniqueName + ')'; - const params = '?filter=' + filter + '&filetype='; - const base_url = domainObject.telemetry.dataProductContentUrl + params; - datum.emd_url = base_url + '.emd'; - datum.emd_preview = base_url + '.emd'; - datum.dat_url = base_url + '.dat'; - datum.txt_url = base_url.replace('DataProductContent', 'DataProductView') + '.dat'; - } - }); - return results; - }); - } - return promise; - } -}; - -// CHANNEL ALARM PROVIDER -const channelAlarmProvider = { - supportsRequest: function (domainObject, options) { - return ( - domainObject.identifier.namespace === 'vista-channel-alarms' && - domainObject.telemetry && - domainObject.telemetry.channelHistoricalUrl && - domainObject.telemetry.alarmLevel - ); - }, - request: function (domainObject, options, params) { - setMaxResults(domainObject, options, params); - setSortFilter(params); - - const dnQueryParams = JSON.parse(JSON.stringify(params)); - const euQueryParams = JSON.parse(JSON.stringify(params)); - - if (domainObject.telemetry.alarmLevel === 'any') { - dnQueryParams.filter.dn_alarm_state__in = ['RED', 'YELLOW']; - euQueryParams.filter.eu_alarm_state__in = ['RED', 'YELLOW']; - } else { - dnQueryParams.filter.dn_alarm_state = domainObject.telemetry.alarmLevel.toUpperCase(); - - euQueryParams.filter.eu_alarm_state = domainObject.telemetry.alarmLevel.toUpperCase(); - } - - const dataTable = mcws.dataTable(domainObject.telemetry.channelHistoricalUrl, { - signal: options.signal - }); - - return Promise.all([dataTable.read(dnQueryParams), dataTable.read(euQueryParams)]).then( - (results) => { - return results[0].concat(results[1]); - } - ); - } -}; - -// COMMAND EVENTS PROVIDER -const commandEventsProvider = { - supportsRequest: function (domainObject, options) { - return domainObject.telemetry && !!domainObject.telemetry.commandEventUrl; - }, - request: function (domainObject, options, params) { - setMaxResults(domainObject, options, params); - params.sort = 'event_time'; - setSortFilter(params); - - if (options.domain === 'ert') { - params.filter.event_time__gte = params.filter[options.domain + '__gte']; - params.filter.event_time__lte = params.filter[options.domain + '__lte']; - } - - delete params.filter[options.domain + '__gte']; - delete params.filter[options.domain + '__lte']; - - return mcws - .dataTable(domainObject.telemetry.commandEventUrl, { signal: options.signal }) - .read(params) - .then( - (res) => { - return res; - }, - (errorResponse) => { - if (errorResponse.status === 400) { - throw errorResponse; - } - - return []; // TODO: better handling due to error. - } - ); - }, - exclusiveDomains: ['ert'] -}; - -// HEADER CHANNELS HISTORICAL PROVIDER -const headerChannelsHistoricalProvider = { - supportsRequest: function (domainObject, request) { - return ( - domainObject.type === types.HeaderChannel.key && - domainObject.telemetry && - domainObject.telemetry.channelHistoricalUrl - ); - }, - request: function (domainObject, options, params) { - params.filter.channel_id = domainObject.telemetry.channel_id; - setMaxResults(domainObject, options, params); - setSortFilter(params); - - return mcws - .dataTable(domainObject.telemetry.channelHistoricalUrl, { signal: options.signal }) - .read(params); - } -}; - -// CHANNEL HISTORICAL PROVIDER -const channelHistoricalProvider = { - supportsRequest: function (domainObject, request) { - return ( - domainObject.type === types.Channel.key && - domainObject.telemetry && - domainObject.telemetry.channelHistoricalUrl && - (request.strategy === 'minmax' ? !domainObject.telemetry.channelMinMaxUrl : true) && - request.size !== 1 - ); - }, - batchRequest: function (batch) { - const requests = Object.values(batch.requestsById); - const params = requests[0].params; - const options = requests[0].options; - - params.select = - '(dn,eu,channel_id,ert,scet,sclk,lst,record_type,dn_alarm_state,eu_alarm_state,module,realtime,dss_id)'; - params.filter.channel_id__in = requests.map((req) => req.domainObject.telemetry.channel_id); - setSortFilter(params); - - mcws - .dataTable(requests[0].domainObject.telemetry.channelHistoricalUrl, { - signal: options.signal - }) - .read(params) - .then((res) => { - const valuesByChannelId = groupBy(res, 'channel_id'); - const toFulfill = keyBy(requests, (req) => req.domainObject.telemetry.channel_id); - - Object.entries(valuesByChannelId).forEach(([id, values]) => { - toFulfill[id].resolve(values); - delete toFulfill[id]; - }); - Object.values(toFulfill).forEach((request) => { - request.resolve([]); - }); - }) - .catch((reason) => { - requests.forEach((request) => request.reject(reason)); - }); - }, - request: function (domainObject, options, params) { - params.filter.channel_id = domainObject.telemetry.channel_id; - setSortFilter(params); - setMaxResults(domainObject, options, params); - - return mcws - .dataTable(domainObject.telemetry.channelHistoricalUrl, { signal: options.signal }) - .read(params); - } -}; - -if (window.openmctMCWSConfig?.batchHistoricalChannelQueries === true) { - channelHistoricalProvider.batchId = function (domainObject, options) { - return [domainObject.telemetry.channelHistoricalUrl, options.domain, options.filters]; - }; -} - // Combine all providers into array const PROVIDERS = [ channelLADProvider, @@ -349,26 +25,6 @@ const PROVIDERS = [ channelHistoricalProvider ]; -function isLADQuery(options) { - return options.strategy === 'latest'; -} - -function setMaxResults(domainObject, options, params) { - if ( - domainObject.telemetry.mcwsVersion >= 3.2 && - options.strategy !== 'comprehensive' && - window.openmctMCWSConfig?.maxResults !== undefined - ) { - params.max_records = window.openmctMCWSConfig.maxResults; - } -} - -function setSortFilter(params) { - if (window.openmctMCWSConfig?.disableSortParam === true) { - delete params.sort; - } -} - function padTime(time) { if (time < 10) { return `0${time}`; diff --git a/src/historical/providers/ChannelAlarmProvider.js b/src/historical/providers/ChannelAlarmProvider.js new file mode 100644 index 0000000..227cca2 --- /dev/null +++ b/src/historical/providers/ChannelAlarmProvider.js @@ -0,0 +1,42 @@ +import mcws from 'services/mcws/mcws.js'; +import { setSortFilter, setMaxResults } from '../../utils/utils.js'; + +class ChannelAlarmProvider { + supportsRequest(domainObject, options) { + return ( + domainObject.identifier.namespace === 'vista-channel-alarms' && + domainObject.telemetry && + domainObject.telemetry.channelHistoricalUrl && + domainObject.telemetry.alarmLevel + ); + } + + request(domainObject, options, params) { + setMaxResults(domainObject, options, params); + setSortFilter(params); + + const dnQueryParams = JSON.parse(JSON.stringify(params)); + const euQueryParams = JSON.parse(JSON.stringify(params)); + + if (domainObject.telemetry.alarmLevel === 'any') { + dnQueryParams.filter.dn_alarm_state__in = ['RED', 'YELLOW']; + euQueryParams.filter.eu_alarm_state__in = ['RED', 'YELLOW']; + } else { + dnQueryParams.filter.dn_alarm_state = domainObject.telemetry.alarmLevel.toUpperCase(); + + euQueryParams.filter.eu_alarm_state = domainObject.telemetry.alarmLevel.toUpperCase(); + } + + const dataTable = mcws.dataTable(domainObject.telemetry.channelHistoricalUrl, { + signal: options.signal + }); + + return Promise.all([dataTable.read(dnQueryParams), dataTable.read(euQueryParams)]).then( + (results) => { + return results[0].concat(results[1]); + } + ); + } +} + +export default new ChannelAlarmProvider(); \ No newline at end of file diff --git a/src/historical/providers/ChannelHistoricalProvider.js b/src/historical/providers/ChannelHistoricalProvider.js new file mode 100644 index 0000000..9ba281b --- /dev/null +++ b/src/historical/providers/ChannelHistoricalProvider.js @@ -0,0 +1,68 @@ +import mcws from 'services/mcws/mcws.js'; +import types from '../../types/types.js'; +import { groupBy, keyBy, setSortFilter, setMaxResults } from '../../utils/utils.js'; + +class ChannelHistoricalProvider { + supportsRequest(domainObject, request) { + return ( + domainObject.type === types.Channel.key && + domainObject.telemetry && + domainObject.telemetry.channelHistoricalUrl && + (request.strategy === 'minmax' ? !domainObject.telemetry.channelMinMaxUrl : true) && + request.size !== 1 + ); + } + + get batchId() { + if (window.openmctMCWSConfig?.batchHistoricalChannelQueries === true) { + return (domainObject, options) => { + return [domainObject.telemetry.channelHistoricalUrl, options.domain, options.filters]; + }; + } + return undefined; + } + + batchRequest(batch) { + const requests = Object.values(batch.requestsById); + const params = requests[0].params; + const options = requests[0].options; + + params.select = + '(dn,eu,channel_id,ert,scet,sclk,lst,record_type,dn_alarm_state,eu_alarm_state,module,realtime,dss_id)'; + params.filter.channel_id__in = requests.map((req) => req.domainObject.telemetry.channel_id); + setSortFilter(params); + + mcws + .dataTable(requests[0].domainObject.telemetry.channelHistoricalUrl, { + signal: options.signal + }) + .read(params) + .then((res) => { + const valuesByChannelId = groupBy(res, 'channel_id'); + const toFulfill = keyBy(requests, (req) => req.domainObject.telemetry.channel_id); + + Object.entries(valuesByChannelId).forEach(([id, values]) => { + toFulfill[id].resolve(values); + delete toFulfill[id]; + }); + Object.values(toFulfill).forEach((request) => { + request.resolve([]); + }); + }) + .catch((reason) => { + requests.forEach((request) => request.reject(reason)); + }); + } + + request(domainObject, options, params) { + params.filter.channel_id = domainObject.telemetry.channel_id; + setSortFilter(params); + setMaxResults(domainObject, options, params); + + return mcws + .dataTable(domainObject.telemetry.channelHistoricalUrl, { signal: options.signal }) + .read(params); + } +} + +export default new ChannelHistoricalProvider(); \ No newline at end of file diff --git a/src/historical/providers/CommandEventsProvider.js b/src/historical/providers/CommandEventsProvider.js new file mode 100644 index 0000000..118cab7 --- /dev/null +++ b/src/historical/providers/CommandEventsProvider.js @@ -0,0 +1,44 @@ +import mcws from 'services/mcws/mcws.js'; +import { setSortFilter, setMaxResults } from '../../utils/utils.js'; + +class CommandEventsProvider { + constructor() { + this.exclusiveDomains = ['ert']; + } + + supportsRequest(domainObject, options) { + return domainObject.telemetry && !!domainObject.telemetry.commandEventUrl; + } + + request(domainObject, options, params) { + setMaxResults(domainObject, options, params); + params.sort = 'event_time'; + setSortFilter(params); + + if (options.domain === 'ert') { + params.filter.event_time__gte = params.filter[options.domain + '__gte']; + params.filter.event_time__lte = params.filter[options.domain + '__lte']; + } + + delete params.filter[options.domain + '__gte']; + delete params.filter[options.domain + '__lte']; + + return mcws + .dataTable(domainObject.telemetry.commandEventUrl, { signal: options.signal }) + .read(params) + .then( + (res) => { + return res; + }, + (errorResponse) => { + if (errorResponse.status === 400) { + throw errorResponse; + } + + return []; // TODO: better handling due to error. + } + ); + } +} + +export default new CommandEventsProvider(); \ No newline at end of file diff --git a/src/historical/providers/DataProductProvider.js b/src/historical/providers/DataProductProvider.js new file mode 100644 index 0000000..1370226 --- /dev/null +++ b/src/historical/providers/DataProductProvider.js @@ -0,0 +1,39 @@ +import mcws from 'services/mcws/mcws.js'; +import { setSortFilter, setMaxResults } from '../../utils/utils.js'; + +class DataProductProvider { + supportsRequest(domainObject, options) { + return domainObject.telemetry && !!domainObject.telemetry.dataProductUrl; + } + + request(domainObject, options, params) { + setMaxResults(domainObject, options, params); + setSortFilter(params); + + const promise = mcws + .dataTable(domainObject.telemetry.dataProductUrl, { signal: options.signal }) + .read(params); + + if (domainObject.type === 'vista.dataProducts') { + return promise.then((results) => { + results.forEach((datum) => { + const sessionId = datum.session_id; + if (datum.unique_name !== undefined) { + const uniqueName = datum.unique_name.replace(/\.dat$/, ''); + const filter = '(session_id=' + sessionId + ',unique_name=' + uniqueName + ')'; + const params = '?filter=' + filter + '&filetype='; + const base_url = domainObject.telemetry.dataProductContentUrl + params; + datum.emd_url = base_url + '.emd'; + datum.emd_preview = base_url + '.emd'; + datum.dat_url = base_url + '.dat'; + datum.txt_url = base_url.replace('DataProductContent', 'DataProductView') + '.dat'; + } + }); + return results; + }); + } + return promise; + } +} + +export default new DataProductProvider(); \ No newline at end of file diff --git a/src/historical/providers/EvrProvider.js b/src/historical/providers/EvrProvider.js new file mode 100644 index 0000000..e6b20b2 --- /dev/null +++ b/src/historical/providers/EvrProvider.js @@ -0,0 +1,53 @@ +import mcws from 'services/mcws/mcws.js'; +import { setSortFilter, setMaxResults, isLADQuery } from '../../utils/utils.js'; + +class EvrProvider { + supportsRequest(domainObject, options) { + const hasTelemetry = Boolean(domainObject.telemetry); + const hasEvrHistoricalUrl = + hasTelemetry && Boolean(domainObject.telemetry.evrHistoricalUrl); + const hasEvrLADUrl = hasTelemetry && Boolean(domainObject.telemetry.evrLADUrl); + + return hasEvrHistoricalUrl || (hasEvrLADUrl && isLADQuery(options)); + } + + request(domainObject, options, params) { + const evrHistoricalUrl = domainObject.telemetry.evrHistoricalUrl; + const evrLADUrl = domainObject.telemetry.evrLADUrl; + + let url = evrHistoricalUrl; + + if (evrLADUrl && isLADQuery(options)) { + url = evrLADUrl; + params.lad_type = params.sort; + + /* + * For LAD queries by name, + * MCWS and AMPCS also requires a level + */ + if (domainObject.telemetry.evr_name) { + params.filter.level = '*'; + } + } + + if (domainObject.telemetry.level) { + params.filter.level = domainObject.telemetry.level; + } + + if (domainObject.telemetry.module) { + params.filter.module = domainObject.telemetry.module; + } + + if (domainObject.telemetry.evr_name) { + params.filter.name = domainObject.telemetry.evr_name; + } + + setMaxResults(domainObject, options, params); + + setSortFilter(params); + + return mcws.dataTable(url, { signal: options.signal }).read(params); + } +} + +export default new EvrProvider(); \ No newline at end of file diff --git a/src/historical/providers/HeaderChannelsHistoricalProvider.js b/src/historical/providers/HeaderChannelsHistoricalProvider.js new file mode 100644 index 0000000..df5050d --- /dev/null +++ b/src/historical/providers/HeaderChannelsHistoricalProvider.js @@ -0,0 +1,25 @@ +import mcws from 'services/mcws/mcws.js'; +import types from '../../types/types.js'; +import { setSortFilter, setMaxResults } from '../../utils/utils.js'; + +class HeaderChannelsHistoricalProvider { + supportsRequest(domainObject, request) { + return ( + domainObject.type === types.HeaderChannel.key && + domainObject.telemetry && + domainObject.telemetry.channelHistoricalUrl + ); + } + + request(domainObject, options, params) { + params.filter.channel_id = domainObject.telemetry.channel_id; + setMaxResults(domainObject, options, params); + setSortFilter(params); + + return mcws + .dataTable(domainObject.telemetry.channelHistoricalUrl, { signal: options.signal }) + .read(params); + } +} + +export default new HeaderChannelsHistoricalProvider(); \ No newline at end of file diff --git a/src/historical/providers/MinMaxProvider.js b/src/historical/providers/MinMaxProvider.js new file mode 100644 index 0000000..be4c2cd --- /dev/null +++ b/src/historical/providers/MinMaxProvider.js @@ -0,0 +1,65 @@ +import mcws from 'services/mcws/mcws.js'; +import types from '../../types/types.js'; +import { groupBy, keyBy, setSortFilter } from '../../utils/utils.js'; + +class MinMaxProvider { + constructor() { + this.isMinMaxProvider = true; + } + + supportsRequest(domainObject, request) { + return ( + domainObject.type === types.Channel.key && + domainObject.telemetry && + domainObject.telemetry.channelMinMaxUrl && + request.size > 1 && + request.strategy === 'minmax' + ); + } + + batchId(domainObject, options) { + return [ + domainObject.telemetry.channelLADUrl, + options.domain, + options.start, + options.end, + options.size + ]; + } + + batchRequest(batch) { + const requests = Object.values(batch.requestsById); + const params = requests[0].params; + const options = requests[0].options; + + params.minmax = + '(' + [requests[0].options.size, requests[0].options.domain, 'eu_or_dn'].join(',') + ')'; + params.select = + '(dn,eu,channel_id,ert,scet,sclk,lst,record_type,eu_or_dn,dn_alarm_state,eu_alarm_state)'; + params.filter.channel_id__in = requests.map((req) => req.domainObject.telemetry.channel_id); + setSortFilter(params); + + mcws + .dataTable(requests[0].domainObject.telemetry.channelMinMaxUrl, { + signal: options.signal + }) + .read(params) + .then((res) => { + const valuesByChannelId = groupBy(res, 'channel_id'); + const toFulfill = keyBy(requests, (req) => req.domainObject.telemetry.channel_id); + + Object.entries(valuesByChannelId).forEach(([id, values]) => { + toFulfill[id].resolve(values); + delete toFulfill[id]; + }); + Object.values(toFulfill).forEach((request) => { + request.resolve([]); + }); + }) + .catch((reason) => { + requests.forEach((request) => request.reject(reason)); + }); + } +} + +export default new MinMaxProvider(); \ No newline at end of file diff --git a/src/utils/utils.js b/src/utils/utils.js index 2f7f218..e34d5b5 100644 --- a/src/utils/utils.js +++ b/src/utils/utils.js @@ -10,7 +10,6 @@ export function groupBy(array, key) { }, {}); } -// Helper function to replace lodash keyBy export function keyBy(array, key) { return array.reduce((result, item) => { const groupKey = typeof key === 'function' ? key(item) : item[key]; @@ -23,4 +22,37 @@ export function setSortFilter(params) { if (window.openmctMCWSConfig?.disableSortParam === true) { delete params.sort; } -} \ No newline at end of file +} + +export function isLADQuery(options) { + return options.strategy === 'latest'; +} + +export function setMaxResults(domainObject, options, params) { + if ( + domainObject.telemetry.mcwsVersion >= 3.2 && + options.strategy !== 'comprehensive' && + window.openmctMCWSConfig?.maxResults !== undefined + ) { + params.max_records = window.openmctMCWSConfig.maxResults; + } +} + +export function setSortFilter(params) { + if (window.openmctMCWSConfig?.disableSortParam === true) { + delete params.sort; + } +} + +// Helper function to replace lodash debounce +export function debounce(func, wait = 0) { + let timeout; + return function executedFunction(...args) { + const later = () => { + clearTimeout(timeout); + func(...args); + }; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + }; +} From 78cb8c133a14c8f05311242072627933339b8c33 Mon Sep 17 00:00:00 2001 From: Jamie V Date: Thu, 4 Dec 2025 09:32:10 -0800 Subject: [PATCH 19/22] handle streamworker loading at buildtime --- src/realtime/MCWSStreamWorker.js | 22 ++++++++++++++-------- src/realtime/MCWSStreamWorkerScript.js | 24 +++++++++++++----------- src/utils/utils.js | 6 ------ 3 files changed, 27 insertions(+), 25 deletions(-) diff --git a/src/realtime/MCWSStreamWorker.js b/src/realtime/MCWSStreamWorker.js index 3fe559e..0b95aaa 100644 --- a/src/realtime/MCWSStreamWorker.js +++ b/src/realtime/MCWSStreamWorker.js @@ -1,11 +1,17 @@ -const workerInstance = new Worker( - new URL('./MCWSStreamWorkerScript.js', import.meta.url), - { - type: 'classic', // Your worker uses an IIFE format, so 'classic' is appropriate - name: 'MCWSStreamWorker' - } -); +// const workerInstance = new Worker( +// new URL('./MCWSStreamWorkerScript.js', import.meta.url), +// { +// type: 'classic', // Your worker uses an IIFE format, so 'classic' is appropriate +// name: 'MCWSStreamWorker' +// } +// ); + +import MCWSStreamWorkerScript from './MCWSStreamWorkerScript.js'; + +const blob = new Blob([MCWSStreamWorkerScript], { type: 'application/javascript' }); + +const objectUrl = URL.createObjectURL(blob); export default function run() { - return workerInstance; + return new Worker(objectUrl); } diff --git a/src/realtime/MCWSStreamWorkerScript.js b/src/realtime/MCWSStreamWorkerScript.js index 7824a27..a8431ab 100644 --- a/src/realtime/MCWSStreamWorkerScript.js +++ b/src/realtime/MCWSStreamWorkerScript.js @@ -1,3 +1,4 @@ +export default ` (function (self, WebSocket) { 'use strict'; @@ -98,7 +99,7 @@ }; if (this.property !== 'some_undefined_property') { - filter[this.property] = `(${Object.keys(this.subscribers).join(',')})`; + filter[this.property] = "(${Object.keys(this.subscribers).join(',')})"; } if (this.extraFilterTerms) { @@ -110,17 +111,17 @@ if (this.globalFilters) { Object.entries(this.globalFilters).forEach(([key, value]) => { if (filter[key]) { - console.warn(`Global filter not applied for existing persisted filter for ${key}.`); + console.warn("Global filter not applied for existing persisted filter for ${key}."); } else { filter[key] = value; } }); } - return `filter=(${Object.keys(filter) + return "filter=(${Object.keys(filter) .filter((key) => Boolean(filter[key])) - .map((key) => `${key}=${filter[key]}`) - .join(',')})`; + .map((key) => "${key}=${filter[key]}") + .join(',')})"; } /** @@ -190,7 +191,7 @@ } // Create a new WebSocket connection with the updated query parameters - this.socket = new WebSocket(`${this.url}?${this.getQueryString()}`); + this.socket = new WebSocket("${this.url}?${this.getQueryString()}"); // close old socket in new socket open to ensure // no data is lost @@ -252,8 +253,8 @@ * the set of subscriptions changes. * * Methods may be invoked by posting a message to the worker - * with an object containing `key` and `value` properties, where - * `key` is the method name and `value` is the argument to provide. + * with an object containing "key" and "value" properties, where + * "key" is the method name and "value" is the argument to provide. */ class MCWSStreamWorker { constructor() { @@ -287,12 +288,12 @@ extraFilterTerms && Object.keys(extraFilterTerms) .sort() - .map((filterKey) => `${filterKey}=${extraFilterTerms[filterKey]}`) + .map((filterKey) => "${filterKey}=${extraFilterTerms[filterKey]}") .join('&'); - let cacheKey = `${url}__${property}`; + let cacheKey = "${url}__${property}"; if (filterComponent?.length > 0) { - cacheKey += `__${filterComponent}`; + cacheKey += "__${filterComponent}"; } return cacheKey; @@ -344,3 +345,4 @@ } }; })(self, WebSocket); +`; diff --git a/src/utils/utils.js b/src/utils/utils.js index e34d5b5..d7b71e4 100644 --- a/src/utils/utils.js +++ b/src/utils/utils.js @@ -38,12 +38,6 @@ export function setMaxResults(domainObject, options, params) { } } -export function setSortFilter(params) { - if (window.openmctMCWSConfig?.disableSortParam === true) { - delete params.sort; - } -} - // Helper function to replace lodash debounce export function debounce(func, wait = 0) { let timeout; From 47bc7b1cd01744f295879d392d6dc16696a0dfef Mon Sep 17 00:00:00 2001 From: Jamie V Date: Thu, 4 Dec 2025 09:42:32 -0800 Subject: [PATCH 20/22] rever to see if can run --- src/realtime/MCWSStreamWorker.js | 22 ++++++++-------------- src/realtime/MCWSStreamWorkerScript.js | 24 +++++++++++------------- 2 files changed, 19 insertions(+), 27 deletions(-) diff --git a/src/realtime/MCWSStreamWorker.js b/src/realtime/MCWSStreamWorker.js index 0b95aaa..3fe559e 100644 --- a/src/realtime/MCWSStreamWorker.js +++ b/src/realtime/MCWSStreamWorker.js @@ -1,17 +1,11 @@ -// const workerInstance = new Worker( -// new URL('./MCWSStreamWorkerScript.js', import.meta.url), -// { -// type: 'classic', // Your worker uses an IIFE format, so 'classic' is appropriate -// name: 'MCWSStreamWorker' -// } -// ); - -import MCWSStreamWorkerScript from './MCWSStreamWorkerScript.js'; - -const blob = new Blob([MCWSStreamWorkerScript], { type: 'application/javascript' }); - -const objectUrl = URL.createObjectURL(blob); +const workerInstance = new Worker( + new URL('./MCWSStreamWorkerScript.js', import.meta.url), + { + type: 'classic', // Your worker uses an IIFE format, so 'classic' is appropriate + name: 'MCWSStreamWorker' + } +); export default function run() { - return new Worker(objectUrl); + return workerInstance; } diff --git a/src/realtime/MCWSStreamWorkerScript.js b/src/realtime/MCWSStreamWorkerScript.js index a8431ab..7824a27 100644 --- a/src/realtime/MCWSStreamWorkerScript.js +++ b/src/realtime/MCWSStreamWorkerScript.js @@ -1,4 +1,3 @@ -export default ` (function (self, WebSocket) { 'use strict'; @@ -99,7 +98,7 @@ export default ` }; if (this.property !== 'some_undefined_property') { - filter[this.property] = "(${Object.keys(this.subscribers).join(',')})"; + filter[this.property] = `(${Object.keys(this.subscribers).join(',')})`; } if (this.extraFilterTerms) { @@ -111,17 +110,17 @@ export default ` if (this.globalFilters) { Object.entries(this.globalFilters).forEach(([key, value]) => { if (filter[key]) { - console.warn("Global filter not applied for existing persisted filter for ${key}."); + console.warn(`Global filter not applied for existing persisted filter for ${key}.`); } else { filter[key] = value; } }); } - return "filter=(${Object.keys(filter) + return `filter=(${Object.keys(filter) .filter((key) => Boolean(filter[key])) - .map((key) => "${key}=${filter[key]}") - .join(',')})"; + .map((key) => `${key}=${filter[key]}`) + .join(',')})`; } /** @@ -191,7 +190,7 @@ export default ` } // Create a new WebSocket connection with the updated query parameters - this.socket = new WebSocket("${this.url}?${this.getQueryString()}"); + this.socket = new WebSocket(`${this.url}?${this.getQueryString()}`); // close old socket in new socket open to ensure // no data is lost @@ -253,8 +252,8 @@ export default ` * the set of subscriptions changes. * * Methods may be invoked by posting a message to the worker - * with an object containing "key" and "value" properties, where - * "key" is the method name and "value" is the argument to provide. + * with an object containing `key` and `value` properties, where + * `key` is the method name and `value` is the argument to provide. */ class MCWSStreamWorker { constructor() { @@ -288,12 +287,12 @@ export default ` extraFilterTerms && Object.keys(extraFilterTerms) .sort() - .map((filterKey) => "${filterKey}=${extraFilterTerms[filterKey]}") + .map((filterKey) => `${filterKey}=${extraFilterTerms[filterKey]}`) .join('&'); - let cacheKey = "${url}__${property}"; + let cacheKey = `${url}__${property}`; if (filterComponent?.length > 0) { - cacheKey += "__${filterComponent}"; + cacheKey += `__${filterComponent}`; } return cacheKey; @@ -345,4 +344,3 @@ export default ` } }; })(self, WebSocket); -`; From a35f407cabf82cb42e94f4b94503588cf9966322 Mon Sep 17 00:00:00 2001 From: Jamie V Date: Thu, 4 Dec 2025 11:16:37 -0800 Subject: [PATCH 21/22] configuration documentation --- CONFIGURATION.md | 359 +++++++++++++++++++++++++++++++++++++++++++++++ README.md | 2 + 2 files changed, 361 insertions(+) create mode 100644 CONFIGURATION.md diff --git a/CONFIGURATION.md b/CONFIGURATION.md new file mode 100644 index 0000000..fc3e8e1 --- /dev/null +++ b/CONFIGURATION.md @@ -0,0 +1,359 @@ +# Plugin Options Recipe Example +When including the Open MCT for MCWS Plugin into your Open MCT project, the following are available configuration options. +```yaml +- openmct-mcws-plugin: + npmPackage: NASA-AMMOS/openmct-mcws#omm-plugin + options: + camUrl: '' + mcwsUrl: http://localhost:8090/mcws-test + namespaces: + - key: 'r50-dev' + name: 'R5.0 Shared' + url: '' + - userNamespace: true + key: 'r50-dev' + name: 'R5.0 Users' + url: '' + theme: 'Snow' + venueAware: + enabled: false + venues: 'ExampleVenueDefinitions.json' + taxonomy: + evrDefaultBackgroundColor: null + evrDefaultForegroundColor: null + evrBackgroundColorByLevel: + FATAL: '#ff0000' + WARNING_HI: '#ff7f24' + WARNING_LO: '#ffff00' + COMMAND: '#00bfff' + ACTIVITY_HI: '#6d6d6d' + ACTIVITY_LO: '#dcdcdc' + DIAGNOSTIC: '#00ff00' + EVR_UNKNOWN: '#00ff00' + FAULT: '#ff0000' + WARNING: '#ff7f24' + evrForegroundColorByLevel: + FATAL: '#ffffff' + WARNING_HI: '#000000' + WARNING_LO: '#000000' + COMMAND: '#ffffff' + ACTIVITY_HI: '#ffffff' + ACTIVITY_LO: '#000000' + DIAGNOSTIC: '#000000' + EVR_UNKNOWN: '#000000' + FAULT: '#ffffff' + WARNING: '#000000' + time: + defaultMode: 'fixed' + utcFormat: 'utc.day-of-year' + lmstEpoch: null + subscriptionMCWSFilterDelay: 100 + timeSystems: ['scet', 'ert'] + allowRealtime: true + allowLAD: true + records: 10 + maxResults: 10000 + sessionHistoricalMaxResults: 100 + batchHistoricalChannelQueries: false + disableSortParam: false + messageStreamUrl: '' + messageTypeFilters: [] + frameAccountabilityExpectedVcidList: [] + queryTimespanLimit: null + globalStalenessInterval: null + customFormatters: [] + sessions: + historicalSessionFilter: + disable: false + maxRecords: 100 + denyUnfilteredQueries: false + realtimeSession: + disable: false + globalFilters: [] + tablePerformanceOptions: + telemetryMode: 'unlimited' + persistModeChange: false + rowLimit: 50 + useDeveloperStorage: true + proxyUrl: 'http://localhost:8080/' + assetPath: 'node_modules/openmct/dist' +``` + +# Configuration Guide + +## Required Options + +#### `camUrl` +- **Type**: `string` +- **Required**: Yes +- **Description**: URL to the CAM server this instance uses for authentication. + +#### `mcwsUrl` +- **Type**: `string` +- **Required**: Yes +- **Description**: URL for MCWS root. + +#### `namespaces` +- **Type**: `array` +- **Required**: Yes +- **Description**: Each entry adds a root folder to the object tree. + +**Namespace Properties:** +- `key` (string, required): Unique key for this namespace. +- `name` (string, required): User-visible name for this namespace. +- `url` (string, required): URL to MCWS namespace which will store the contents of the namespace. +- `userNamespace` (boolean, optional, defaults to `false`): If `true`, this namespace will be used to create per-user folders. + + +## Basic Configuration + +### Theme + +#### `theme` +- **Type**: `string` +- **Default**: `'Snow'` +- **Options**: `'Snow'`, `'Espresso'`, or `'Maelstrom'` +- **Description**: Sets the theme for the Open MCT interface. + +### Venue Aware Configuration + +#### `venueAware` +- **Type**: `object` +- **Added in**: R4.0 +- **Description**: Options here enable venue aware mode and allow configuration of venue aware mode. Venue aware configuration allows pre-configuration with a list of venues and datasets such that users are prompted to select either an active venue or a historical session that they'd like to review. Enabling venue-aware mode disables manual creation of datasets. + +**Properties:** +- `enabled` (boolean): Enable or disable venue aware mode. Options: `true`, `false`. +- `venues` (string or array): Either a list of venue definitions or a URL for a JSON venue definition file. If a URL is provided, it will be queried at run time to determine the venues available. An example of a JSON venue definition file is provided in "ExampleVenueDefinitions.json". + +### Taxonomy Configuration + +#### `taxonomy` +- **Type**: `object` +- **Description**: Options here affect how various telemetry types are displayed. + +**Properties:** +- `evrDefaultBackgroundColor` (string or `null`): Default background color for EVRs. Set to `null` to use the theme default. Otherwise, specify a hex string for an RGB color, e.g. `#ababab`. +- `evrDefaultForegroundColor` (string or `null`): Default foreground color for EVRs. Set to `null` to use the theme default. Otherwise, specify a hex string for an RGB color, e.g. `#ababab`. +- `evrBackgroundColorByLevel` (object): Specify the background color of EVRs by level. If a level is not defined here, it will use the default specified above. Keys are specific EVR levels, and values must be a hex string for an RGB color, e.g. `#ababab`. + - Supported levels (FSW Specific): `FATAL`, `WARNING_HI`, `WARNING_LO`, `COMMAND`, `ACTIVITY_HI`, `ACTIVITY_LO`, `DIAGNOSTIC`, `EVR_UNKNOWN` + - Supported levels (SSE Specific): `FAULT`, `WARNING` +- `evrForegroundColorByLevel` (object): Specify the foreground color of EVRs by level. If a level is not defined here, it will use the default specified above. Keys are specific EVR levels, and values must be a hex string for an RGB color, e.g. `#ababab`. + - Supported levels (FSW Specific): `FATAL`, `WARNING_HI`, `WARNING_LO`, `COMMAND`, `ACTIVITY_HI`, `ACTIVITY_LO`, `DIAGNOSTIC`, `EVR_UNKNOWN` + - Supported levels (SSE Specific): `FAULT`, `WARNING` + +### Time Configuration + +#### `time` +- **Type**: `object` +- **Description**: Settings for time APIs and formats. + +**Properties:** +- `defaultMode` (string): Default conductor mode. Available options: + - `'fixed'`: Fixed time bounds. + - `'utc.local'`: Follow local UTC clock. Only available when `allowRealtime` is `true` and `scet` or `ert` timeSystems are available. + - `'scet.lad'`: Follow latest scet seen in telemetry data. Only available when `allowLAD` is `true` and `scet` timeSystem is enabled. + - `'ert.lad'`: Follow latest ert seen in telemetry data. Only available when `allowLAD` is `true` and `ert` timeSystem is enabled. + - `'sclk.lad'`: Follow latest sclk seen in telemetry data. Only available when `allowLAD` is `true` and `sclk` timeSystem is enabled. + - `'msl.sol.lad'`: Follow latest mslsol seen in telemetry data. Only available when `allowLAD` is `true` and `mslsol` timeSystem is enabled. +- `utcFormat` (string): Available options: + - `'utc.day-of-year'`: Format as `2015-015T12:34:56.999` + - `'utc'`: Format as `2015-01-15T12:34:56.999` +- `lmstEpoch` (number or `null`): Epoch date for LMST Time System. It has to be a Date.UTC instance, e.g. `Date.UTC(2020, 2, 18, 0, 0, 0)`. Note: In YAML, this would need to be converted to a timestamp number. +- `subscriptionMCWSFilterDelay` (number): Delay in milliseconds for combining filters for the same subscription endpoint connection. Smaller value = quicker display of realtime data (e.g., 10ms in a low latency environment). Higher value = avoids potentially creating and subsequently tearing down new websocket connections if filter changes are happening faster than server response times (e.g., 100ms+ in a high latency environment). +- `timeSystems` (array or array of objects): Specify the time systems to use. Options are `'scet'`, `'ert'`, `'sclk'`, `'msl.sol'` and `'lmst'`. + + **Basic Configuration**: Simple array of time system keys, e.g. `['scet', 'ert']`. + + **Advanced Configuration**: Array of objects with timeSystem-specific configurations: + - `key` (string, required): Time system. Options are `'scet'`, `'ert'`, `'sclk'`, `'msl.sol'` and `'lmst'`. + - `limit` (number, optional): Maximum duration between start and end bounds allowed (in milliseconds). + - `modeSettings` (object, optional): Presets for convenience. + - `fixed` (object, optional): Valid objects are `bounds` objects and `presets` array. + - `bounds` (object, optional): Start and end bounds for preset as numbers. `start` and `end` can be declared as a number or a function returning a number. + - `presets` (array, optional): Array of objects consisting of: + - `bounds` (object, required): Start and end bounds. + - `label` (string, required): Label for the preset. + - `realtime` (object, optional): Valid objects are `clockOffsets` and `presets` array. + - `clockOffsets` (object, optional): Start and end relative to active clock. `start` and `end` are numbers relative to active clock's 0. Start is negative, end is positive. + - `presets` (array, optional): Array of preset objects with `bounds` and `label`. + - `lad` (object, optional): Valid objects are `clockOffsets`. + - `clockOffsets` (object, optional): Start and end relative to active clock. `start` and `end` are numbers relative to active clock's 0. Start is negative, end is positive. +- `allowRealtime` (boolean): Whether or not to allow UTC-relative time conductor. +- `allowLAD` (boolean): Whether or not to allow latest data relative time conductor. **Note**: `allowRealtime` must be `true` to use this option. +- `records` (number): Number of previous bounds per timeSystem to save in time conductor history. + +### Query Configuration + +#### `maxResults` +- **Type**: `number` +- **Optional**: Yes +- **Description**: A maximum results limit for historical queries. + +#### `sessionHistoricalMaxResults` +- **Type**: `number` +- **Default**: `100` +- **Description**: A maximum results limit for historical session queries. + +#### `batchHistoricalChannelQueries` +- **Type**: `boolean` +- **Default**: `false` +- **Description**: Set to `true` to batch channel historical queries in telemetry tables. +- **Warning**: **USE WITH CAUTION** - You can more easily overwhelm the backend with a larger single query. + +#### `disableSortParam` +- **Type**: `boolean` +- **Default**: `false` +- **Description**: Enable to not send sort param in historical queries. Only set this configuration to `true` if you are certain you wish to disable backend sort. + +#### `queryTimespanLimit` +- **Type**: `number` or `null` +- **Default**: `null` +- **Description**: Use to warn the user and block historical query when the ert, scet or lmst based time-conductor timespan exceeds set limits. Units are in milliseconds. When set to `null`, user will not be warned and queries will not be blocked. + +### Message and Frame Configuration + +#### `messageStreamUrl` +- **Type**: `string` +- **Default**: `''` +- **Description**: URL used to listen to message stream for StartOfSession and EndOfSession messages. + +#### `messageTypeFilters` +- **Type**: `array` +- **Default**: `[]` +- **Description**: Use to set mission specific filters on messages by message type. + +**Filter Object Properties:** +- `value` (string): Message type code value. +- `label` (string): User-visible label for identifying this filter option. + +**Example:** +messageTypeFilters: + - value: 'LossOfSync' + label: 'Loss of Sync' + - value: 'InSync' + label: 'In Sync' + +#### `frameAccountabilityExpectedVcidList` +- **Type**: `array` +- **Default**: `[]` +- **Description**: Use to set up expected VCID's in the frame event stream. Frame Accountability View will highlight the unexpected VC's in orange. + +**Example:** +frameAccountabilityExpectedVcidList: + - 234223 + - 234234 + - 223423 + +### Staleness Configuration + +#### `globalStalenessInterval` +- **Type**: `number` or `null` +- **Default**: `null` +- **Description**: Time since last received realtime datum. Any datum that is received after the set timespan will have a stale (`isStale`) property set. Units are in milliseconds. When set to `null`, there will be no global staleness timespan set. + +### Custom Formatters + +#### `customFormatters` +- **Type**: `array` +- **Default**: `[]` +- **Description**: Register custom formatters for use in Telemetry View in Display Layout's. Custom Formatters need to be an object with a unique String `key` property and a `format` function that accepts a value and returns formatted value. Custom formatters can be accessed in Display Layout's format inspector view, with a pre-pended `&`, e.g. the `'hello-world'` formatter can be accessed by `&hello-world`. + +**Note**: In YAML, functions cannot be directly represented. You would need to define these in JavaScript code that processes the YAML configuration. + +**Example Structure:** +customFormatters: + - key: 'hello-world' + # format function would need to be defined in JavaScript + +### Session Configuration + +#### `sessions` +- **Type**: `object` +- **Description**: Use to set deployment specific session configuration. + +**Properties:** +- `historicalSessionFilter` (object): Configuration for historical session filtering. + - `disable` (boolean): To disable historical session filtering. + - `maxRecords` (number): A number greater than 0, for maximum historical session records to be returned. + - `denyUnfilteredQueries` (boolean): Whether to deny unfiltered queries. +- `realtimeSession` (object): Configuration for realtime sessions. + - `disable` (boolean): To disable realtime sessions. **Note**: This will disable all websocket connections. + +### Global Filters + +#### `globalFilters` +- **Type**: `array` +- **Optional**: Yes +- **Description**: Enable global filters for ALL telemetry requests that support the filter. Telemetry filters modify the `filter` field in queries to MCWS. + +**How to use:** +The global filters will be available from the Global Filters indicator. Enable a filter by selecting the desired filter from the dropdown and hitting update. Outgoing requests that use the `filter` parameter to MCWS will be modified with your filter. For example, selecting 'A side' will ensure that the filter parameter in MCWS includes: `vcid='1,2,3'`. Note that poorly formatted filters may not pass MCWS API validation. + +**Filter Object Properties:** +- `key` (string, required): Filter column, e.g. `vcid`. +- `name` (string, required): Identifier of the filter in the selection window. +- `icon` (string, optional): Icon identifier, e.g. `'icon-flag'`. Not implemented - potentially icon for minimized filter list. +- `filter` (object, required): Filter object to implement. + - `comparator` (string, required): Currently supports `'equals'`. + - `singleSelectionThreshold` (boolean, required): Currently supports `true` only. + - `defaultLabel` (string, optional): Defaults to `'None'`. Label to show if filter inactive. + - `possibleValues` (array, required): List of values and labels for filter. + - `label` (string, required): Label to show in filter selection dropdown. + - `value` (string, required): Value to set parameter to in filtered query. + +**Example:** +globalFilters: + - name: 'VCID' + key: 'vcid' + icon: 'icon-flag' + filter: + comparator: 'equals' + singleSelectionThreshold: true + defaultLabel: "A & B" + possibleValues: + - label: 'A Side' + value: '1,2,3' + - label: 'B Side' + value: '4,5,6' + - name: 'Realtime' + key: 'realtime' + filter: + comparator: 'equals' + singleSelectionThreshold: true + defaultLabel: "REC & RLT" + possibleValues: + - label: 'Realtime' + value: true + - label: 'Recorded' + value: false + +### Telemetry Table Performance Configuration + +#### `tablePerformanceOptions` +- **Type**: `object` +- **Description**: Table Performance Mode Configuration. Can increase performance by limiting the maximum rows retained and displayed by tables. Affects all bounded table types such as Telemetry and EVR tables. Does not affect latest available tables such as Channel tables. + +**Properties:** +- `telemetryMode` (string): Performance mode limits the maximum table rows. Options: `'performance'`, `'unlimited'`. +- `persistModeChange` (boolean): Whether changes in the UI are persisted with the table. +- `rowLimit` (number): The maximum number of rows in performance mode. + +### Developer Settings + +**Warning**: Do not modify these unless you know what they do! + +#### `proxyUrl` +- **Type**: `string` +- **Default**: `'http://localhost:8080/'` +- **Description**: Developer setting for proxy URL. + +#### `useDeveloperStorage` +- **Type**: `boolean` +- **Default**: `true` +- **Description**: Developer setting - enables developer storage mode. Do not modify unless you know what it does. + +#### `assetPath` +- **Type**: `string` +- **Default**: `'node_modules/openmct/dist'` +- **Description**: Developer setting for asset path. diff --git a/README.md b/README.md index 2311523..c531bf6 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,8 @@ Development configurations and customizations are available by editing `recipes/ 2. `mcwsUrl`: The url to the MCWS server. 3. In the `namespaces` configuration, `url`, the path to the MCWS persistence spaces, are required. +Further configuration documentation can be found in the `CONFIGURATION.md`. + ## Development ### Prerequisite From 5ca5a2930f8c594a45223d5e4385be79179ebe09 Mon Sep 17 00:00:00 2001 From: Andrew Henry Date: Mon, 8 Dec 2025 11:44:54 -0800 Subject: [PATCH 22/22] Updated plugin --- plugin.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugin.js b/plugin.js index df45df9..6a474d3 100644 --- a/plugin.js +++ b/plugin.js @@ -112,7 +112,8 @@ export default function openmctMCWSPlugin(options) { persistModeChange: false, rowLimit: 50 }, - assetPath: 'node_modules/openmct/dist' + assetPath: 'node_modules/openmct/dist', + mcwsPluginAssetPath: 'node_modules/openmct-mcws-plugin/dist' }; // Deep merge function @@ -262,7 +263,7 @@ export default function openmctMCWSPlugin(options) { // load the css file const link = document.createElement('link'); link.rel = 'stylesheet'; - link.href = `node_modules/openmct-mcws-plugin/dist/openmct-mcws-plugin.css`; + link.href = `${config.mcwsPluginAssetPath}/openmct-mcws-plugin.css`; document.head.appendChild(link); }