From 03156e335275de1dafbc2a816e98006afdf249bf Mon Sep 17 00:00:00 2001 From: Jeremy Mordkoff Date: Sat, 30 Sep 2017 21:42:44 -0400 Subject: [PATCH] update from RIFT as of 696b75d2fe9fb046261b08c616f1bcf6c0b54a9b third try Signed-off-by: Jeremy Mordkoff Change-Id: Ib11aa03a2eff5a53c808342508a5d7bee7b202b8 --- .gitignore | 2 + BUILD.sh | 121 +++ CMakeLists.txt | 11 +- Dockerfile | 25 +- Jenkinsfile | 1 - manifest/LICENSE | 1 + postinst | 21 + python-osmclient/.gitignore | 90 -- python-osmclient/Makefile | 2 - python-osmclient/README.md | 73 -- python-osmclient/osmclient/__init__.py | 0 python-osmclient/osmclient/common/OsmAPI.py | 436 -------- python-osmclient/osmclient/common/__init__.py | 0 .../osmclient/scripts/__init__.py | 0 python-osmclient/osmclient/scripts/osm.py | 191 ---- python-osmclient/setup.py | 17 - python-osmclient/stdeb.cfg | 2 - skyquake/.storybook/config.js | 6 +- skyquake/framework/core/api_utils/auth.js | 145 +++ .../framework/core/api_utils/constants.js | 12 +- skyquake/framework/core/api_utils/csrf.js | 67 ++ .../core/api_utils/openidconnect_config.json | 9 + skyquake/framework/core/api_utils/sockets.js | 15 +- skyquake/framework/core/api_utils/utils.js | 93 +- .../core/modules/api/appConfigAPI.js | 119 +++ .../core/modules/api/configuration.js | 22 +- .../modules/api/descriptorModelMetaAPI.js | 4 +- .../framework/core/modules/api/modelAPI.js | 359 +++++++ .../core/modules/api/projectManagementAPI.js | 297 ++++++ .../framework/core/modules/api/restconf.js | 2 +- .../framework/core/modules/api/schemaAPI.js | 100 ++ .../framework/core/modules/api/sessions.js | 294 ++++++ .../core/modules/api/userManagementAPI.js | 530 ++++++++++ .../core/modules/navigation_manager.js | 19 +- .../framework/core/modules/routes/auth.js | 99 ++ .../core/modules/routes/configuration.js | 33 - .../core/modules/routes/navigation.js | 73 +- .../core/modules/routes/projectManagement.js | 85 ++ .../framework/core/modules/routes/sessions.js | 74 ++ .../core/modules/routes/userManagement.js | 85 ++ skyquake/framework/core/views/home.ejs | 5 + .../framework/core/views/idpconnectfail.ejs | 43 + skyquake/framework/plugin-index.html | 13 + skyquake/framework/source/SourceCache.js | 96 ++ skyquake/framework/source/model/index.js | 7 + .../framework/source/model/modelActions.js | 31 + .../framework/source/model/modelSource.js | 128 +++ skyquake/framework/source/schema/index.js | 7 + .../framework/source/schema/schemaActions.js | 31 + .../framework/source/schema/schemaSource.js | 95 ++ skyquake/framework/style/_colors.scss | 21 +- skyquake/framework/style/core.css | 4 +- skyquake/framework/style/img/osm_header.png | Bin 131139 -> 8767 bytes .../framework/style/img/osm_header_253x50.png | Bin 9663 -> 11795 bytes .../style/img/osm_header_506x100.png | Bin 22955 -> 25054 bytes skyquake/framework/utils/appConfiguration.js | 42 + skyquake/framework/utils/roleConstants.js | 31 + skyquake/framework/utils/utils.js | 115 ++- skyquake/framework/widgets/button/button.scss | 23 +- .../framework/widgets/button/sq-button.jsx | 24 +- skyquake/framework/widgets/components.js | 350 +------ .../widgets/form_controls/formControls.jsx | 113 ++ .../widgets/form_controls/formControls.scss | 112 +- .../framework/widgets/form_controls/input.jsx | 134 +++ .../widgets/form_controls/selectOption.jsx | 59 +- .../widgets/form_controls/textInput.jsx | 60 +- skyquake/framework/widgets/header/header.scss | 38 +- .../launchpadOperationalStatus.jsx | 4 +- skyquake/framework/widgets/panel/panel.jsx | 22 +- skyquake/framework/widgets/panel/panel.scss | 13 + .../skyquake_container/eventCenter.jsx | 104 +- .../skyquake_container/skyquakeApp.scss | 36 +- .../skyquake_container/skyquakeComponent.jsx | 2 +- .../skyquake_container/skyquakeContainer.jsx | 61 +- .../skyquakeContainerActions.js | 8 +- .../skyquakeContainerSource.js | 75 +- .../skyquakeContainerStore.js | 80 +- .../skyquake_container/skyquakeNav.jsx | 227 ---- .../skyquake_container/skyquakeRouter.jsx | 15 +- .../widgets/skyquake_nav/skyquakeNav.jsx | 377 +++++++ .../widgets/skyquake_nav/skyquakeNav.scss | 127 +++ .../skyquake_notification/netConfErrors.js | 58 ++ .../skyquakeNotification.jsx | 89 ++ .../widgets/skyquake_rbac/skyquakeRBAC.jsx | 119 +++ skyquake/package.json | 9 +- skyquake/plugins/CMakeLists.txt | 9 +- skyquake/plugins/about/api/about.js | 6 +- skyquake/plugins/about/config.json | 8 +- skyquake/plugins/about/scripts/build.sh | 16 +- skyquake/plugins/about/scripts/install.sh | 4 +- skyquake/plugins/about/src/about.jsx | 37 +- .../about/webpack.production.config.js | 5 +- skyquake/plugins/accounts/api/accounts.js | 169 ++- .../api/cloud_account/cloudAccount.js | 28 +- .../accounts/api/config_agent/configAgent.js | 20 +- .../accounts/api/sdn_account/sdnAccount.js | 20 +- skyquake/plugins/accounts/config.json | 11 +- .../routes.js => accounts/config_routes.js} | 0 skyquake/plugins/accounts/images/brocade.png | Bin 0 -> 15704 bytes skyquake/plugins/accounts/routes.js | 6 +- skyquake/plugins/accounts/scripts/build.sh | 14 +- skyquake/plugins/accounts/scripts/install.sh | 4 +- .../plugins/accounts/src/account/account.jsx | 348 +++++-- .../accounts/src/account/accountActions.js | 3 +- .../accounts/src/account/accountSource.js | 73 +- .../accounts/src/account/accountStore.js | 296 +++++- .../src/account/accountsDashboard.jsx | 17 +- .../src/account_sidebar/accountSidebar.jsx | 140 ++- .../src/account_sidebar/accountSidebar.scss | 51 +- .../accounts/webpack.production.config.js | 5 +- skyquake/plugins/admin/CMakeLists.txt | 52 + skyquake/plugins/admin/api/admin.js | 141 +++ skyquake/plugins/admin/config.json | 28 + skyquake/plugins/admin/package.json | 58 ++ skyquake/plugins/admin/routes.js | 30 + skyquake/plugins/admin/scripts/build.sh | 22 + skyquake/plugins/admin/scripts/install.sh | 49 + skyquake/plugins/admin/server.js | 63 ++ skyquake/plugins/admin/src/AdminStore.js | 57 + skyquake/plugins/admin/src/admin.jsx | 132 +++ skyquake/plugins/admin/src/admin.scss | 76 ++ skyquake/plugins/admin/src/adminActions.js | 25 + skyquake/plugins/admin/src/adminSource.js | 51 + .../admin/src/components/ActionBar.jsx | 30 + .../admin/src/components/ChoiceCard.jsx | 32 + .../admin/src/components/ChoiceColumn.jsx | 22 + .../admin/src/components/ColumnCard.jsx | 23 + .../admin/src/components/ContainerCard.jsx | 23 + .../admin/src/components/ContainerColumn.jsx | 63 ++ .../admin/src/components/EditorDialog.jsx | 249 +++++ .../admin/src/components/ExplorerColumn.jsx | 23 + .../admin/src/components/LeafGroup.jsx | 45 + .../plugins/admin/src/components/ListCard.jsx | 25 + .../admin/src/components/ListColumn.jsx | 45 + .../admin/src/components/ListEntryCard.jsx | 23 + .../admin/src/components/ListEntryColumn.jsx | 9 + .../admin/src/components/ListStack.jsx | 41 + .../admin/src/components/LoadingCard.jsx | 21 + .../admin/src/components/LoadingColumn.jsx | 21 + .../admin/src/components/ModelExplorer.jsx | 181 ++++ .../src/components/editor/LeafEditor.jsx | 300 ++++++ .../admin/src/components/editor/LeafField.jsx | 144 +++ .../admin/src/components/editor/Select.jsx | 50 + .../src/components/editor/resolveLeafRef.js | 284 +++++ skyquake/plugins/admin/src/main.js | 11 + .../plugins/admin/src/store/ModelStore.js | 265 +++++ skyquake/plugins/admin/src/yang/leaf-utils.js | 100 ++ .../plugins/admin/src/yang/property-utils.js | 45 + .../admin/webpack.production.config.js | 92 ++ skyquake/plugins/composer/api/composer.js | 212 ++-- .../composer/api/packageFileHandler.js | 40 +- skyquake/plugins/composer/config.json | 6 + skyquake/plugins/composer/package.json | 16 +- skyquake/plugins/composer/routes.js | 40 +- skyquake/plugins/composer/scripts/build.sh | 14 +- .../composer/src/schemas/yang/confd2model.js | 490 ++++----- .../src/schemas/yang/mano-types.yang.src | 2 +- .../src/actions/CatalogDataSourceActions.js | 11 +- .../src/src/actions/ComposerAppActions.js | 19 +- .../src/actions/DescriptorEditorActions.js | 42 + .../src/src/actions/PanelResizeAction.js | 4 +- skyquake/plugins/composer/src/src/alt.js | 6 +- .../composer/src/src/components/Button.js | 10 +- .../src/src/components/CanvasPanel.js | 77 +- .../src/src/components/CanvasPanelTray.js | 29 +- .../src/components/CatalogItemCanvasEditor.js | 33 +- .../components/CatalogItemDetailsEditor.js | 39 +- .../src/src/components/CatalogItems.js | 69 +- .../src/src/components/CatalogPanel.js | 50 +- .../src/src/components/CatalogPanelToolbar.js | 26 +- .../src/src/components/ComposerApp.js | 400 ++++---- .../src/src/components/ComposerAppToolbar.js | 20 +- .../ConfigPrimitiveParameters.js | 354 +++++++ .../src/src/components/DetailsPanel.js | 105 +- .../src/components/DetailsPanelToolbar.jsx | 63 ++ .../EditDescriptorModelProperties.js | 970 +++++------------- .../EditForwardingGraphPaths.js | 33 +- .../components/NavigateDescriptorErrors.jsx | 73 ++ .../components/NavigateDescriptorModel.jsx | 238 +++++ .../composer/src/src/components/RiftHeader.js | 8 - .../components/filemanager/FileManager.jsx | 35 +- .../filemanager/FileManagerSource.js | 10 +- .../composer/src/src/components/messages.js | 10 +- .../src/src/components/model/Choice.jsx | 57 + .../src/src/components/model/Container.jsx | 39 + .../components/model/EditDescriptorUtils.js | 81 ++ .../src/src/components/model/LeafEditor.jsx | 298 ++++++ .../src/src/components/model/LeafField.jsx | 150 +++ .../src/src/components/model/List.jsx | 105 ++ .../src/components/model/ListItemAsLink.jsx | 45 + .../src/components/model/ModelBreadcrumb.jsx | 43 + .../src/src/components/model/PanelHeader.jsx | 63 ++ .../src/components/model/PropertyCrumb.jsx | 76 ++ .../src/components/model/PropertyNavigate.jsx | 64 ++ .../src/components/model/PropertyPanel.jsx | 49 + .../src/components/model/RemoveItemButton.jsx | 42 + .../src/src/components/model/Select.jsx | 62 ++ .../libraries/FileManagerUploadDropZone.js | 2 +- .../src/src/libraries/PackageManagerApi.js | 113 -- .../src/src/libraries/TooltipManager.js | 14 +- .../composer/src/src/libraries/UniqueId.js | 12 +- .../src/libraries/graph/DescriptorGraph.js | 9 +- .../layouts/RelationsAndNetworksLayout.js | 16 +- .../src/libraries/model/DescriptorModel.js | 15 +- .../libraries/model/DescriptorModelFactory.js | 25 +- .../libraries/model/DescriptorModelFields.js | 1 + .../model/DescriptorModelMetaFactory.js | 7 +- .../model/DescriptorModelMetaProperty.js | 7 +- .../model/DescriptorModelSerializer.js | 5 +- .../model/descriptors/ConnectionPoint.js | 4 +- .../ConstituentVnfdConnectionPoint.js | 4 +- .../model/descriptors/InternalVirtualLink.js | 4 +- .../model/descriptors/NetworkService.js | 43 +- .../descriptors/VirtualNetworkFunction.js | 2 +- .../VirtualNetworkFunctionAccessPoint.js | 57 + .../VirtualNetworkFunctionAccessPointMap.js | 99 ++ .../VirtualNetworkFunctionReadOnlyWrapper.js | 6 +- .../composer/src/src/libraries/utils.js | 74 +- .../src/src/sources/CatalogDataSource.js | 4 +- .../sources/CatalogPackageManagerSource.js | 13 +- .../src/src/stores/CatalogDataStore.js | 313 +++--- .../src/stores/CatalogPackageManagerStore.js | 30 +- .../src/src/stores/ComposerAppStore.js | 623 ++++++++--- .../composer/src/src/styles/Button.scss | 4 +- .../composer/src/src/styles/CanvasPanel.scss | 1 + .../src/src/styles/CanvasPanelTray.scss | 44 +- .../src/styles/CatalogItemDetailsEditor.scss | 12 + .../composer/src/src/styles/CatalogItems.scss | 2 +- .../composer/src/src/styles/DetailsPanel.scss | 29 +- .../src/src/styles/DetailsPanelErrors.scss | 28 + .../src/src/styles/DetailsPanelToolbar.scss | 50 + .../src/styles/EditConfigParameterMap.scss | 357 +++++++ .../styles/EditDescriptorModelProperties.scss | 106 +- .../composer/src/src/styles/_main.scss | 2 +- .../composer/webpack.production.config.js | 5 +- skyquake/plugins/config/api/ro.js | 94 -- skyquake/plugins/config/config.json | 15 - .../config/images/OpenDaylight_logo.png | Bin 8851 -> 0 bytes skyquake/plugins/config/images/aws.png | Bin 6165 -> 0 bytes skyquake/plugins/config/images/juju.svg | 64 -- skyquake/plugins/config/images/openmano.png | Bin 57137 -> 0 bytes .../config/images/openstack-horizontal.png | Bin 2687 -> 0 bytes skyquake/plugins/config/images/openstack.png | Bin 9804 -> 0 bytes skyquake/plugins/config/images/riftio.png | Bin 78162 -> 0 bytes skyquake/plugins/config/scripts/build.sh | 18 - .../plugins/config/src/dashboard/config.scss | 271 ----- .../config/src/dashboard/configActions.js | 9 - .../config/src/dashboard/configSource.js | 93 -- .../config/src/dashboard/configStore.js | 135 --- .../config/src/dashboard/dashboard.jsx | 139 --- .../plugins/config/src/dashboard/inputs.jsx | 401 -------- skyquake/plugins/debug/api/debug.js | 2 +- skyquake/plugins/debug/config.json | 8 +- skyquake/plugins/debug/scripts/build.sh | 14 +- skyquake/plugins/debug/scripts/install.sh | 4 +- .../debug/webpack.production.config.js | 5 +- .../plugins/goodbyeworld/scripts/build.sh | 14 +- .../goodbyeworld/webpack.production.config.js | 3 +- skyquake/plugins/helloworld/scripts/build.sh | 14 +- .../helloworld/webpack.production.config.js | 3 +- skyquake/plugins/launchpad/api/launchpad.js | 368 ++++--- skyquake/plugins/launchpad/config.json | 18 +- skyquake/plugins/launchpad/package.json | 2 - skyquake/plugins/launchpad/routes.js | 7 + skyquake/plugins/launchpad/scripts/build.sh | 14 +- skyquake/plugins/launchpad/scripts/install.sh | 4 +- .../src/instantiate/instantiateDashboard.jsx | 22 +- .../src/instantiate/instantiateDashboard.scss | 3 +- .../instantiate/instantiateInputParams.jsx | 151 ++- .../src/instantiate/instantiateParameters.jsx | 10 +- .../instantiateSelectDescriptorPanel.jsx | 34 +- .../instantiateSelectDescriptorPanel.scss | 19 +- .../src/instantiate/instantiateStore.js | 279 ++--- .../launchNetworkServiceActions.js | 4 +- .../instantiate/launchNetworkServiceSource.js | 53 +- .../plugins/launchpad/src/launchpad-create.js | 132 --- skyquake/plugins/launchpad/src/launchpad.jsx | 15 +- .../launchpad/src/launchpadFleetSource.js | 6 +- .../src/launchpad_card/launchpad-card.js | 370 ------- .../src/launchpad_card/launchpadCard.jsx | 18 +- .../launchpadCardCloudAccount.jsx | 53 +- .../src/launchpad_card/launchpadHeader.jsx | 24 +- .../src/launchpad_card/launchpad_card.scss | 166 ++- .../launchpad_card/nsrConfigPrimitives.jsx | 9 +- .../src/launchpad_card/nsrScalingGroups.jsx | 8 +- .../launchpad_card/vnfrConfigPrimitives.jsx | 14 +- .../monitoringParamComponents.js | 16 +- .../monitoring_params/monitoring_params.scss | 3 +- .../launchpad/src/nsCardPanel/nsCardPanel.jsx | 5 +- .../launchpad/src/nsListPanel/nsListPanel.jsx | 9 +- .../launchpad/src/recordViewer/recordCard.jsx | 23 +- .../launchpad/src/recordViewer/recordView.jsx | 4 +- .../src/recordViewer/recordViewSource.js | 28 +- .../src/recordViewer/recordViewStore.js | 2 +- .../src/topologyL2View/topologyL2Source.js | 2 +- .../src/topologyView/topologySource.js | 12 +- .../nsVirtualLinkCreateSource.js | 6 +- .../src/virtual_links/nsVirtualLinks.jsx | 42 +- .../plugins/launchpad/src/vnfr/vnfrSource.js | 4 +- .../launchpad/webpack.production.config.js | 5 +- skyquake/plugins/logging/api/logging.js | 8 +- skyquake/plugins/logging/api/transforms.js | 3 +- skyquake/plugins/logging/config.json | 8 +- skyquake/plugins/logging/package.json | 1 + skyquake/plugins/logging/scripts/build.sh | 14 +- skyquake/plugins/logging/scripts/install.sh | 4 +- skyquake/plugins/logging/src/loggingSource.js | 3 + skyquake/plugins/logging/src/loggingStore.js | 1 + .../logging/webpack.production.config.js | 5 +- skyquake/plugins/logging/yarn.lock | 218 +++- .../plugins/project_management/CMakeLists.txt | 38 + .../plugins/project_management/config.json | 19 + .../package.json | 0 skyquake/plugins/project_management/routes.js | 12 + .../project_management/scripts/build.sh | 8 + .../project_management/scripts/install.sh | 34 + .../{config => project_management}/server.js | 2 + .../src/dashboard/dashboard.jsx | 475 +++++++++ .../src/dashboard/projectMgmt.scss | 347 +++++++ .../src/dashboard/projectMgmtActions.js | 36 + .../src/dashboard/projectMgmtSource.js | 178 ++++ .../src/dashboard/projectMgmtStore.js | 299 ++++++ .../src/main.js | 0 .../webpack.production.config.js | 9 +- .../{config => redundancy}/CMakeLists.txt | 6 +- skyquake/plugins/redundancy/api/redundancy.js | 368 +++++++ skyquake/plugins/redundancy/config.json | 41 + skyquake/plugins/redundancy/package.json | 55 + skyquake/plugins/redundancy/routes.js | 56 + skyquake/plugins/redundancy/scripts/build.sh | 8 + .../{config => redundancy}/scripts/install.sh | 2 +- skyquake/plugins/redundancy/server.js | 51 + .../redundancy/src/dashboard/config.jsx | 227 ++++ .../redundancy/src/dashboard/dashboard.jsx | 70 ++ .../redundancy/src/dashboard/redundancy.scss | 382 +++++++ .../src/dashboard/redundancyActions.js | 38 + .../src/dashboard/redundancySource.js | 246 +++++ .../src/dashboard/redundancyStore.js | 270 +++++ .../redundancy/src/dashboard/sites.jsx | 408 ++++++++ .../redundancy/src/dashboard/status.jsx | 198 ++++ .../redundancy/src/dashboard/utils/utils.js | 14 + skyquake/plugins/redundancy/src/main.js | 15 + .../redundancy/webpack.production.config.js | 78 ++ .../plugins/user_management/CMakeLists.txt | 38 + skyquake/plugins/user_management/config.json | 33 + skyquake/plugins/user_management/package.json | 55 + skyquake/plugins/user_management/routes.js | 12 + .../plugins/user_management/scripts/build.sh | 8 + .../user_management/scripts/install.sh | 34 + skyquake/plugins/user_management/server.js | 51 + .../src/dashboard/dashboard.jsx | 509 +++++++++ .../src/dashboard/userMgmt.scss | 221 ++++ .../src/dashboard/userMgmtActions.js | 26 + .../src/dashboard/userMgmtSource.js | 151 +++ .../src/dashboard/userMgmtStore.js | 221 ++++ skyquake/plugins/user_management/src/main.js | 15 + .../platformRoleManagement.jsx | 355 +++++++ .../platformRoleManagement.scss | 337 ++++++ .../platformRoleManagementActions.js | 38 + .../platformRoleManagementSource.js | 121 +++ .../platformRoleManagementStore.js | 275 +++++ .../src/userProfile/userProfile.jsx | 387 +++++++ .../src/userProfile/userProfileActions.js | 20 + .../src/userProfile/userProfileSource.js | 94 ++ .../src/userProfile/userProfileStore.js | 211 ++++ .../webpack.production.config.js | 78 ++ skyquake/scripts/build-dev.sh | 76 ++ skyquake/scripts/build-plugin.sh | 61 ++ skyquake/scripts/build.sh | 8 +- skyquake/scripts/handle_plugin_node_modules | 58 ++ skyquake/scripts/launch_ui.sh | 69 +- skyquake/skyquake.js | 602 ++++++----- skyquake/tests/stories/FileManager.js | 19 + 373 files changed, 22587 insertions(+), 6775 deletions(-) create mode 100755 BUILD.sh create mode 100755 postinst delete mode 100644 python-osmclient/.gitignore delete mode 100644 python-osmclient/Makefile delete mode 100644 python-osmclient/README.md delete mode 100644 python-osmclient/osmclient/__init__.py delete mode 100644 python-osmclient/osmclient/common/OsmAPI.py delete mode 100644 python-osmclient/osmclient/common/__init__.py delete mode 100644 python-osmclient/osmclient/scripts/__init__.py delete mode 100755 python-osmclient/osmclient/scripts/osm.py delete mode 100644 python-osmclient/setup.py delete mode 100644 python-osmclient/stdeb.cfg create mode 100644 skyquake/framework/core/api_utils/auth.js create mode 100644 skyquake/framework/core/api_utils/csrf.js create mode 100644 skyquake/framework/core/api_utils/openidconnect_config.json create mode 100644 skyquake/framework/core/modules/api/appConfigAPI.js create mode 100644 skyquake/framework/core/modules/api/modelAPI.js create mode 100644 skyquake/framework/core/modules/api/projectManagementAPI.js create mode 100644 skyquake/framework/core/modules/api/schemaAPI.js create mode 100644 skyquake/framework/core/modules/api/sessions.js create mode 100644 skyquake/framework/core/modules/api/userManagementAPI.js create mode 100644 skyquake/framework/core/modules/routes/auth.js create mode 100644 skyquake/framework/core/modules/routes/projectManagement.js create mode 100644 skyquake/framework/core/modules/routes/sessions.js create mode 100644 skyquake/framework/core/modules/routes/userManagement.js create mode 100644 skyquake/framework/core/views/home.ejs create mode 100644 skyquake/framework/core/views/idpconnectfail.ejs create mode 100644 skyquake/framework/plugin-index.html create mode 100644 skyquake/framework/source/SourceCache.js create mode 100644 skyquake/framework/source/model/index.js create mode 100644 skyquake/framework/source/model/modelActions.js create mode 100644 skyquake/framework/source/model/modelSource.js create mode 100644 skyquake/framework/source/schema/index.js create mode 100644 skyquake/framework/source/schema/schemaActions.js create mode 100644 skyquake/framework/source/schema/schemaSource.js create mode 100644 skyquake/framework/utils/appConfiguration.js create mode 100644 skyquake/framework/utils/roleConstants.js create mode 100644 skyquake/framework/widgets/form_controls/formControls.jsx create mode 100644 skyquake/framework/widgets/form_controls/input.jsx delete mode 100644 skyquake/framework/widgets/skyquake_container/skyquakeNav.jsx create mode 100644 skyquake/framework/widgets/skyquake_nav/skyquakeNav.jsx create mode 100644 skyquake/framework/widgets/skyquake_nav/skyquakeNav.scss create mode 100644 skyquake/framework/widgets/skyquake_notification/netConfErrors.js create mode 100644 skyquake/framework/widgets/skyquake_notification/skyquakeNotification.jsx create mode 100644 skyquake/framework/widgets/skyquake_rbac/skyquakeRBAC.jsx rename skyquake/plugins/{config/routes.js => accounts/config_routes.js} (100%) create mode 100644 skyquake/plugins/accounts/images/brocade.png create mode 100644 skyquake/plugins/admin/CMakeLists.txt create mode 100644 skyquake/plugins/admin/api/admin.js create mode 100644 skyquake/plugins/admin/config.json create mode 100644 skyquake/plugins/admin/package.json create mode 100644 skyquake/plugins/admin/routes.js create mode 100755 skyquake/plugins/admin/scripts/build.sh create mode 100755 skyquake/plugins/admin/scripts/install.sh create mode 100644 skyquake/plugins/admin/server.js create mode 100644 skyquake/plugins/admin/src/AdminStore.js create mode 100644 skyquake/plugins/admin/src/admin.jsx create mode 100644 skyquake/plugins/admin/src/admin.scss create mode 100644 skyquake/plugins/admin/src/adminActions.js create mode 100644 skyquake/plugins/admin/src/adminSource.js create mode 100644 skyquake/plugins/admin/src/components/ActionBar.jsx create mode 100644 skyquake/plugins/admin/src/components/ChoiceCard.jsx create mode 100644 skyquake/plugins/admin/src/components/ChoiceColumn.jsx create mode 100644 skyquake/plugins/admin/src/components/ColumnCard.jsx create mode 100644 skyquake/plugins/admin/src/components/ContainerCard.jsx create mode 100644 skyquake/plugins/admin/src/components/ContainerColumn.jsx create mode 100644 skyquake/plugins/admin/src/components/EditorDialog.jsx create mode 100644 skyquake/plugins/admin/src/components/ExplorerColumn.jsx create mode 100644 skyquake/plugins/admin/src/components/LeafGroup.jsx create mode 100644 skyquake/plugins/admin/src/components/ListCard.jsx create mode 100644 skyquake/plugins/admin/src/components/ListColumn.jsx create mode 100644 skyquake/plugins/admin/src/components/ListEntryCard.jsx create mode 100644 skyquake/plugins/admin/src/components/ListEntryColumn.jsx create mode 100644 skyquake/plugins/admin/src/components/ListStack.jsx create mode 100644 skyquake/plugins/admin/src/components/LoadingCard.jsx create mode 100644 skyquake/plugins/admin/src/components/LoadingColumn.jsx create mode 100644 skyquake/plugins/admin/src/components/ModelExplorer.jsx create mode 100644 skyquake/plugins/admin/src/components/editor/LeafEditor.jsx create mode 100644 skyquake/plugins/admin/src/components/editor/LeafField.jsx create mode 100644 skyquake/plugins/admin/src/components/editor/Select.jsx create mode 100644 skyquake/plugins/admin/src/components/editor/resolveLeafRef.js create mode 100644 skyquake/plugins/admin/src/main.js create mode 100644 skyquake/plugins/admin/src/store/ModelStore.js create mode 100644 skyquake/plugins/admin/src/yang/leaf-utils.js create mode 100644 skyquake/plugins/admin/src/yang/property-utils.js create mode 100644 skyquake/plugins/admin/webpack.production.config.js create mode 100644 skyquake/plugins/composer/src/src/actions/DescriptorEditorActions.js create mode 100644 skyquake/plugins/composer/src/src/components/ConfigPrimitiveParameters/ConfigPrimitiveParameters.js create mode 100644 skyquake/plugins/composer/src/src/components/DetailsPanelToolbar.jsx create mode 100644 skyquake/plugins/composer/src/src/components/NavigateDescriptorErrors.jsx create mode 100644 skyquake/plugins/composer/src/src/components/NavigateDescriptorModel.jsx create mode 100644 skyquake/plugins/composer/src/src/components/model/Choice.jsx create mode 100644 skyquake/plugins/composer/src/src/components/model/Container.jsx create mode 100644 skyquake/plugins/composer/src/src/components/model/EditDescriptorUtils.js create mode 100644 skyquake/plugins/composer/src/src/components/model/LeafEditor.jsx create mode 100644 skyquake/plugins/composer/src/src/components/model/LeafField.jsx create mode 100644 skyquake/plugins/composer/src/src/components/model/List.jsx create mode 100644 skyquake/plugins/composer/src/src/components/model/ListItemAsLink.jsx create mode 100644 skyquake/plugins/composer/src/src/components/model/ModelBreadcrumb.jsx create mode 100644 skyquake/plugins/composer/src/src/components/model/PanelHeader.jsx create mode 100644 skyquake/plugins/composer/src/src/components/model/PropertyCrumb.jsx create mode 100644 skyquake/plugins/composer/src/src/components/model/PropertyNavigate.jsx create mode 100644 skyquake/plugins/composer/src/src/components/model/PropertyPanel.jsx create mode 100644 skyquake/plugins/composer/src/src/components/model/RemoveItemButton.jsx create mode 100644 skyquake/plugins/composer/src/src/components/model/Select.jsx delete mode 100644 skyquake/plugins/composer/src/src/libraries/PackageManagerApi.js create mode 100644 skyquake/plugins/composer/src/src/libraries/model/descriptors/VirtualNetworkFunctionAccessPoint.js create mode 100644 skyquake/plugins/composer/src/src/libraries/model/descriptors/VirtualNetworkFunctionAccessPointMap.js create mode 100644 skyquake/plugins/composer/src/src/styles/CatalogItemDetailsEditor.scss create mode 100644 skyquake/plugins/composer/src/src/styles/DetailsPanelErrors.scss create mode 100644 skyquake/plugins/composer/src/src/styles/DetailsPanelToolbar.scss create mode 100644 skyquake/plugins/composer/src/src/styles/EditConfigParameterMap.scss delete mode 100644 skyquake/plugins/config/api/ro.js delete mode 100644 skyquake/plugins/config/config.json delete mode 100644 skyquake/plugins/config/images/OpenDaylight_logo.png delete mode 100644 skyquake/plugins/config/images/aws.png delete mode 100644 skyquake/plugins/config/images/juju.svg delete mode 100644 skyquake/plugins/config/images/openmano.png delete mode 100644 skyquake/plugins/config/images/openstack-horizontal.png delete mode 100644 skyquake/plugins/config/images/openstack.png delete mode 100644 skyquake/plugins/config/images/riftio.png delete mode 100755 skyquake/plugins/config/scripts/build.sh delete mode 100644 skyquake/plugins/config/src/dashboard/config.scss delete mode 100644 skyquake/plugins/config/src/dashboard/configActions.js delete mode 100644 skyquake/plugins/config/src/dashboard/configSource.js delete mode 100644 skyquake/plugins/config/src/dashboard/configStore.js delete mode 100644 skyquake/plugins/config/src/dashboard/dashboard.jsx delete mode 100644 skyquake/plugins/config/src/dashboard/inputs.jsx delete mode 100644 skyquake/plugins/launchpad/src/launchpad-create.js delete mode 100644 skyquake/plugins/launchpad/src/launchpad_card/launchpad-card.js create mode 100644 skyquake/plugins/project_management/CMakeLists.txt create mode 100644 skyquake/plugins/project_management/config.json rename skyquake/plugins/{config => project_management}/package.json (100%) create mode 100644 skyquake/plugins/project_management/routes.js create mode 100755 skyquake/plugins/project_management/scripts/build.sh create mode 100755 skyquake/plugins/project_management/scripts/install.sh rename skyquake/plugins/{config => project_management}/server.js (98%) create mode 100644 skyquake/plugins/project_management/src/dashboard/dashboard.jsx create mode 100644 skyquake/plugins/project_management/src/dashboard/projectMgmt.scss create mode 100644 skyquake/plugins/project_management/src/dashboard/projectMgmtActions.js create mode 100644 skyquake/plugins/project_management/src/dashboard/projectMgmtSource.js create mode 100644 skyquake/plugins/project_management/src/dashboard/projectMgmtStore.js rename skyquake/plugins/{config => project_management}/src/main.js (100%) rename skyquake/plugins/{config => project_management}/webpack.production.config.js (91%) rename skyquake/plugins/{config => redundancy}/CMakeLists.txt (87%) create mode 100644 skyquake/plugins/redundancy/api/redundancy.js create mode 100644 skyquake/plugins/redundancy/config.json create mode 100644 skyquake/plugins/redundancy/package.json create mode 100644 skyquake/plugins/redundancy/routes.js create mode 100755 skyquake/plugins/redundancy/scripts/build.sh rename skyquake/plugins/{config => redundancy}/scripts/install.sh (98%) create mode 100644 skyquake/plugins/redundancy/server.js create mode 100644 skyquake/plugins/redundancy/src/dashboard/config.jsx create mode 100644 skyquake/plugins/redundancy/src/dashboard/dashboard.jsx create mode 100644 skyquake/plugins/redundancy/src/dashboard/redundancy.scss create mode 100644 skyquake/plugins/redundancy/src/dashboard/redundancyActions.js create mode 100644 skyquake/plugins/redundancy/src/dashboard/redundancySource.js create mode 100644 skyquake/plugins/redundancy/src/dashboard/redundancyStore.js create mode 100644 skyquake/plugins/redundancy/src/dashboard/sites.jsx create mode 100644 skyquake/plugins/redundancy/src/dashboard/status.jsx create mode 100644 skyquake/plugins/redundancy/src/dashboard/utils/utils.js create mode 100644 skyquake/plugins/redundancy/src/main.js create mode 100644 skyquake/plugins/redundancy/webpack.production.config.js create mode 100644 skyquake/plugins/user_management/CMakeLists.txt create mode 100644 skyquake/plugins/user_management/config.json create mode 100644 skyquake/plugins/user_management/package.json create mode 100644 skyquake/plugins/user_management/routes.js create mode 100755 skyquake/plugins/user_management/scripts/build.sh create mode 100755 skyquake/plugins/user_management/scripts/install.sh create mode 100644 skyquake/plugins/user_management/server.js create mode 100644 skyquake/plugins/user_management/src/dashboard/dashboard.jsx create mode 100644 skyquake/plugins/user_management/src/dashboard/userMgmt.scss create mode 100644 skyquake/plugins/user_management/src/dashboard/userMgmtActions.js create mode 100644 skyquake/plugins/user_management/src/dashboard/userMgmtSource.js create mode 100644 skyquake/plugins/user_management/src/dashboard/userMgmtStore.js create mode 100644 skyquake/plugins/user_management/src/main.js create mode 100644 skyquake/plugins/user_management/src/platformRoleManagement/platformRoleManagement.jsx create mode 100644 skyquake/plugins/user_management/src/platformRoleManagement/platformRoleManagement.scss create mode 100644 skyquake/plugins/user_management/src/platformRoleManagement/platformRoleManagementActions.js create mode 100644 skyquake/plugins/user_management/src/platformRoleManagement/platformRoleManagementSource.js create mode 100644 skyquake/plugins/user_management/src/platformRoleManagement/platformRoleManagementStore.js create mode 100644 skyquake/plugins/user_management/src/userProfile/userProfile.jsx create mode 100644 skyquake/plugins/user_management/src/userProfile/userProfileActions.js create mode 100644 skyquake/plugins/user_management/src/userProfile/userProfileSource.js create mode 100644 skyquake/plugins/user_management/src/userProfile/userProfileStore.js create mode 100644 skyquake/plugins/user_management/webpack.production.config.js create mode 100755 skyquake/scripts/build-dev.sh create mode 100644 skyquake/scripts/build-plugin.sh create mode 100755 skyquake/scripts/handle_plugin_node_modules create mode 100644 skyquake/tests/stories/FileManager.js diff --git a/.gitignore b/.gitignore index 40934df14..6747eae45 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,10 @@ .DS_Store/ .DS_Store +._.DS_Store err.log out.log node_modules/ npm-debug.log fixtures/ .build +.fuse_* diff --git a/BUILD.sh b/BUILD.sh new file mode 100755 index 000000000..e244b7c7b --- /dev/null +++ b/BUILD.sh @@ -0,0 +1,121 @@ +#!/usr/bin/env bash +# +# Copyright 2016,2017 RIFT.IO Inc +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Author(s): Jeremy Mordkoff, Lezz Giles +# Creation Date: 08/29/2016 +# +# + +# BUILD.sh +# +# This is a top-level build script for OSM SO or UI +# +# Arguments and options: use -h or --help +# +# dependencies -- requires sudo rights + +MODULE=UI + +# Defensive bash programming flags +set -o errexit # Exit on any error +trap 'echo ERROR: Command failed: \"$BASH_COMMAND\"' ERR +set -o nounset # Expanding an unset variable is an error. Variables must be + # set before they can be used. + +############################################################################### +# Options and arguments + +# There +params="$(getopt -o h -l install,help --name "$0" -- "$@")" +if [ $? != 0 ] ; then echo "Failed parsing options." >&2 ; exit 1 ; fi + +eval set -- $params + +installFromPackages=false + +while true; do + case "$1" in + --install) installFromPackages=true; shift;; + -h|--help) + echo + echo "NAME:" + echo " $0" + echo + echo "SYNOPSIS:" + echo " $0 -h|--help" + echo " $0 [--install] [PLATFORM_REPOSITORY] [PLATFORM_VERSION]" + echo + echo "DESCRIPTION:" + echo " Prepare current system to run $MODULE. By default, the system" + echo " is set up to support building $MODULE; optionally, " + echo " $MODULE can be installed from a Debian package repository." + echo + echo " --install: install $MODULE from package" + echo " PLATFORM_REPOSITORY (optional): name of the RIFT.ware repository." + echo " PLATFORM_VERSION (optional): version of the platform packages to be installed." + echo + exit 0;; + --) shift; break;; + *) echo "Not implemented: $1" >&2; exit 1;; + esac +done + +# Turn this on after handling options, so the output doesn't get cluttered. +set -x # Print commands before executing them + +############################################################################### +# Set up repo and version + +PLATFORM_REPOSITORY=${1:-osm-rbac} +PLATFORM_VERSION=${2:-5.1.3.9999.70283} + +############################################################################### +# Main block + +# must be run from the top of a workspace +cd $(dirname $0) + +# enable the right repos +curl http://repos.riftio.com/public/xenial-riftware-public-key | sudo apt-key add - + +# always use the same file name so that updates will overwrite rather than enable a second repo +sudo curl -o /etc/apt/sources.list.d/rift.list http://buildtracker.riftio.com/repo_file/ub16/${PLATFORM_REPOSITORY}/ +sudo apt-get update + +sudo apt install -y --allow-downgrades rw.tools-container-tools=${PLATFORM_VERSION} rw.tools-scripts=${PLATFORM_VERSION} + +if $installFromPackages; then + + # Install module and platform from packages + sudo -H /usr/rift/container_tools/mkcontainer --modes $MODULE --repo ${PLATFORM_REPOSITORY} --rw-version ${PLATFORM_VERSION} + +else + + # Install environment to build module + sudo -H /usr/rift/container_tools/mkcontainer --modes $MODULE-dev --repo ${PLATFORM_REPOSITORY} --rw-version ${PLATFORM_VERSION} + + # Build and install module + make -j16 + sudo make install + +fi + +if [[ $MODULE == SO ]]; then + echo "Creating Service ...." + sudo /usr/rift/bin/create_launchpad_service +fi + + diff --git a/CMakeLists.txt b/CMakeLists.txt index 1c9b5766c..8a21f2fd7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,7 +31,7 @@ cmake_minimum_required(VERSION 2.8) # DO NOT add any code before this and DO NOT # include this file anywhere else ## -include(rift_submodule) +include(rift_submodule NO_POLICY_SCOPE) ## # Submodule specific includes will go here, @@ -53,6 +53,15 @@ rift_add_subdirs( ${subdirs} ) +## +# Only skyquake package contains anything +## +rift_set_component_package_fields( + skyquake + DESCRIPTION "RIFT.ware UI" + POST_INSTALL_SCRIPT "${CMAKE_CURRENT_SOURCE_DIR}/postinst" + ) + ## # This macro adds targets for documentaion, unittests, code coverage and packaging ## diff --git a/Dockerfile b/Dockerfile index 40dc9227f..d576e2ff8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,24 +1,13 @@ FROM ubuntu:16.04 -RUN apt-get update && apt-get -y install python3 curl build-essential +RUN apt-get update && apt-get -y install python3 curl build-essential apt-transport-https sudo RUN curl http://repos.riftio.com/public/xenial-riftware-public-key | apt-key add - && \ - curl -o /etc/apt/sources.list.d/OSM.list http://buildtracker.riftio.com/repo_file/ub16/OSM/ && \ + curl -o /etc/apt/sources.list.d/rift.list http://buildtracker.riftio.com/repo_file/ub16/OSM/ && \ apt-get update && \ - apt-get -y install rw.toolchain-rwbase \ - rw.toolchain-rwtoolchain \ - rw.core.mgmt-mgmt \ - rw.core.util-util \ - rw.core.rwvx-rwvx \ - rw.core.rwvx-rwdts \ - rw.automation.core-RWAUTO \ - rw.tools-container-tools \ - rw.tools-scripts \ - python-cinderclient \ - libxml2-dev \ - libxslt-dev + apt-get -y install \ + rw.tools-container-tools=5.2.0.0.71033 \ + rw.tools-scripts=5.2.0.0.71033 -RUN /usr/rift/container_tools/mkcontainer --modes build --modes ext --repo OSM +RUN /usr/rift/container_tools/mkcontainer --modes UI-dev --repo OSM --rw-version 5.2.0.0.71033 -RUN chmod 777 /usr/rift /usr/rift/usr/share - -RUN rm -rf /tmp/npm-cache +RUN chmod 777 /usr/rift diff --git a/Jenkinsfile b/Jenkinsfile index 99cb40523..ceb06c31f 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -29,4 +29,3 @@ node('docker') { params.GERRIT_PATCHSET_REVISION, params.TEST_INSTALL, params.ARTIFACTORY_SERVER) -} diff --git a/manifest/LICENSE b/manifest/LICENSE index e69de29bb..ab4a98190 100644 --- a/manifest/LICENSE +++ b/manifest/LICENSE @@ -0,0 +1 @@ +ho diff --git a/postinst b/postinst new file mode 100755 index 000000000..602b11041 --- /dev/null +++ b/postinst @@ -0,0 +1,21 @@ +#!/bin/bash + +# +# Copyright 2016 RIFT.IO Inc +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# Post-install script for packaging + +/usr/rift/usr/share/rw.ui/skyquake/scripts/handle_plugin_node_modules diff --git a/python-osmclient/.gitignore b/python-osmclient/.gitignore deleted file mode 100644 index e09c6a053..000000000 --- a/python-osmclient/.gitignore +++ /dev/null @@ -1,90 +0,0 @@ -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python -env/ -build/ -develop-eggs/ -dist/ -deb_dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -*.egg-info/ -.installed.cfg -*.egg - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*,cover -.hypothesis/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -target/ - -# IPython Notebook -.ipynb_checkpoints - -# pyenv -.python-version - -# celery beat schedule file -celerybeat-schedule - -# dotenv -.env - -# virtualenv -venv/ -ENV/ - -# Spyder project settings -.spyderproject - -# Rope project settings -.ropeproject diff --git a/python-osmclient/Makefile b/python-osmclient/Makefile deleted file mode 100644 index 1a2e44884..000000000 --- a/python-osmclient/Makefile +++ /dev/null @@ -1,2 +0,0 @@ -package: - @python setup.py --command-packages=stdeb.command bdist_deb > /dev/null 2>&1 diff --git a/python-osmclient/README.md b/python-osmclient/README.md deleted file mode 100644 index 8d41f2f2b..000000000 --- a/python-osmclient/README.md +++ /dev/null @@ -1,73 +0,0 @@ -# python-osmclient -A python client for osm orchestration - -# Installation - -## Install dependencies -```bash -sudo apt-get install python-dev libcurl4-gnutls-dev python-pip libgnutls-dev python-prettytable   -sudo pip install pycurl -``` - -# Setup -Set the OSM_HOSTNAME variable to the host of the osm server. - -Example -```bash -localhost$ export OSM_HOSTNAME=:8008 -``` - -# Examples - -## upload vnfd -```bash -localhost$ osm upload-package ubuntu_xenial_vnf.tar.gz -{'transaction_id': 'ec12af77-1b91-4c84-b233-60f2c2c16d14'} -localhost$ osm vnfd-list -+--------------------+--------------------+ -| vnfd name | id | -+--------------------+--------------------+ -| ubuntu_xenial_vnfd | ubuntu_xenial_vnfd | -+--------------------+--------------------+ -``` - -## upload nsd -```bash -localhost$ osm upload-package ubuntu_xenial_ns.tar.gz -{'transaction_id': 'b560c9cb-43e1-49ef-a2da-af7aab24ce9d'} -localhost$ osm nsd-list -+-------------------+-------------------+ -| nsd name | id | -+-------------------+-------------------+ -| ubuntu_xenial_nsd | ubuntu_xenial_nsd | -+-------------------+-------------------+ -``` -## vim-list - -```bash -localhost$ osm vim-list -+-------------+-----------------+--------------------------------------+ -| ro-account | datacenter name | uuid | -+-------------+-----------------+--------------------------------------+ -| osmopenmano | openstack-site | 2ea04690-0e4a-11e7-89bc-00163e59ff0c | -+-------------+-----------------+--------------------------------------+ -``` - - -## instantiate ns -```bash -localhost$ osm ns-create ubuntu_xenial_nsd testns openstack-site -{'success': ''} -localhost$ osm ns-list -+------------------+--------------------------------------+-------------------+--------------------+---------------+ -| ns instance name | id | catalog name | operational status | config status | -+------------------+--------------------------------------+-------------------+--------------------+---------------+ -| testns | 6b0d2906-13d4-11e7-aa01-b8ac6f7d0c77 | ubuntu_xenial_nsd | running | configured | -+------------------+--------------------------------------+-------------------+--------------------+---------------+ -``` - -# Bash Completion -python-osmclient uses [click](http://click.pocoo.org/5/). You can setup bash completion by putting this in your .bashrc: - - eval "$(_OSM_COMPLETE=source osm)" - diff --git a/python-osmclient/osmclient/__init__.py b/python-osmclient/osmclient/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/python-osmclient/osmclient/common/OsmAPI.py b/python-osmclient/osmclient/common/OsmAPI.py deleted file mode 100644 index f85e87250..000000000 --- a/python-osmclient/osmclient/common/OsmAPI.py +++ /dev/null @@ -1,436 +0,0 @@ -from io import BytesIO -import pycurl -import json -import pprint -import uuid -from prettytable import PrettyTable -import time - -class OsmAPI(): - def __init__(self,host,upload_port=8443): - if host is None: - raise Exception('missing host specifier') - - self._user='admin' - self._password='admin' - self._host=host - self._upload_port=upload_port - self._url = 'https://{}/'.format(self._host) - self._upload_url = 'https://{}:{}/'.format(self._host.split(':')[0],self._upload_port) - - def get_curl_cmd(self,url): - curl_cmd = pycurl.Curl() - curl_cmd.setopt(pycurl.HTTPGET,1) - curl_cmd.setopt(pycurl.URL, self._url + url ) - curl_cmd.setopt(pycurl.SSL_VERIFYPEER,0) - curl_cmd.setopt(pycurl.SSL_VERIFYHOST,0) - curl_cmd.setopt(pycurl.USERPWD, '{}:{}'.format(self._user,self._password)) - curl_cmd.setopt(pycurl.HTTPHEADER, ['Accept: application/vnd.yand.data+json','Content-Type": "application/vnd.yang.data+json']) - return curl_cmd - - def get_curl_upload_cmd(self,filename): - curl_cmd = pycurl.Curl() - curl_cmd.setopt(pycurl.HTTPPOST,[(('package',(pycurl.FORM_FILE,filename)))]) - curl_cmd.setopt(pycurl.URL, self._upload_url + 'composer/upload?api_server=https://localhost&upload_server=https://' + self._host.split(':')[0]) - curl_cmd.setopt(pycurl.SSL_VERIFYPEER,0) - curl_cmd.setopt(pycurl.SSL_VERIFYHOST,0) - curl_cmd.setopt(pycurl.USERPWD, '{}:{}'.format(self._user,self._password)) - return curl_cmd - - def get_ns_opdata(self,id): - data = BytesIO() - curl_cmd=self.get_curl_cmd('api/operational/ns-instance-opdata/nsr/{}?deep'.format(id)) - curl_cmd.setopt(pycurl.HTTPGET,1) - curl_cmd.setopt(pycurl.WRITEFUNCTION, data.write) - curl_cmd.perform() - curl_cmd.close() - if data.getvalue(): - return json.loads(data.getvalue().decode()) - return None - - def get_ns_catalog(self): - data = BytesIO() - curl_cmd=self.get_curl_cmd('api/running/nsd-catalog/nsd') - curl_cmd.setopt(pycurl.HTTPGET,1) - curl_cmd.setopt(pycurl.WRITEFUNCTION, data.write) - curl_cmd.perform() - curl_cmd.close() - if data.getvalue(): - resp = json.loads(data.getvalue().decode()) - return resp - return {'nsd:nsd': []} - - def get_config_agents(self): - data = BytesIO() - curl_cmd=self.get_curl_cmd('api/config/config-agent') - curl_cmd.setopt(pycurl.HTTPGET,1) - curl_cmd.setopt(pycurl.WRITEFUNCTION, data.write) - curl_cmd.perform() - curl_cmd.close() - if data.getvalue(): - resp = json.loads(data.getvalue().decode()) - if 'rw-config-agent:config-agent' in resp: - return resp['rw-config-agent:config-agent']['account'] - return list() - - def delete_config_agent(self,name): - data = BytesIO() - curl_cmd=self.get_curl_cmd('api/config/config-agent/account/'+name) - curl_cmd.setopt(pycurl.CUSTOMREQUEST, "DELETE") - curl_cmd.setopt(pycurl.WRITEFUNCTION, data.write) - curl_cmd.perform() - curl_cmd.close() - resp = json.loads(data.getvalue().decode()) - pprint.pprint(resp) - - def add_config_agent(self,name,account_type,server,user,secret): - data = BytesIO() - curl_cmd=self.get_curl_cmd('api/config/config-agent') - curl_cmd.setopt(pycurl.POST,1) - curl_cmd.setopt(pycurl.WRITEFUNCTION, data.write) - - postdata={} - postdata['account'] = list() - - account={} - account['name'] = name - account['account-type'] = account_type - account['juju'] = {} - account['juju']['user'] = user - account['juju']['secret'] = secret - account['juju']['ip-address'] = server - postdata['account'].append(account) - - jsondata=json.dumps(postdata) - curl_cmd.setopt(pycurl.POSTFIELDS,jsondata) - curl_cmd.perform() - curl_cmd.close() - resp = json.loads(data.getvalue().decode()) - pprint.pprint(resp) - - def get_ns_instance_list(self): - data = BytesIO() - curl_cmd=self.get_curl_cmd('api/running/ns-instance-config') - curl_cmd.setopt(pycurl.HTTPGET,1) - curl_cmd.setopt(pycurl.WRITEFUNCTION, data.write) - curl_cmd.perform() - curl_cmd.close() - resp = json.loads(data.getvalue().decode()) - return resp['nsr:ns-instance-config'] - - def get_vnf_catalog(self): - data = BytesIO() - curl_cmd=self.get_curl_cmd('api/running/vnfd-catalog/vnfd') - curl_cmd.setopt(pycurl.HTTPGET,1) - curl_cmd.setopt(pycurl.WRITEFUNCTION, data.write) - curl_cmd.perform() - curl_cmd.close() - if data.getvalue(): - resp = json.loads(data.getvalue().decode()) - return resp - return {'vnfd:vnfd': []} - - def get_vcs_info(self): - data = BytesIO() - curl_cmd=self.get_curl_cmd('api/operational/vcs/info') - curl_cmd.setopt(pycurl.HTTPGET,1) - curl_cmd.setopt(pycurl.WRITEFUNCTION, data.write) - curl_cmd.perform() - curl_cmd.close() - if data.getvalue(): - resp = json.loads(data.getvalue().decode()) - return resp['rw-base:info']['components']['component_info'] - return list() - - def get_vnfr_catalog(self): - data = BytesIO() - curl_cmd=self.get_curl_cmd('v1/api/operational/vnfr-catalog/vnfr') - curl_cmd.setopt(pycurl.HTTPGET,1) - curl_cmd.setopt(pycurl.WRITEFUNCTION, data.write) - curl_cmd.perform() - curl_cmd.close() - if data.getvalue(): - resp = json.loads(data.getvalue().decode()) - return resp - return None - - def get_vnf(self,vnf_name): - vnfs=self.get_vnfr_catalog() - for vnf in vnfs['vnfr:vnfr']: - if vnf_name == vnf['name']: - return vnf - return None - - def get_vnf_monitoring(self,vnf_name): - vnf=self.get_vnf(vnf_name) - if vnf is not None: - if 'monitoring-param' in vnf: - return vnf['monitoring-param'] - return None - - def get_ns_monitoring(self,ns_name): - ns=self.get_ns(ns_name) - if ns is None: - raise Exception('cannot find ns {}'.format(ns_name)) - - vnfs=self.get_vnfr_catalog() - if vnfs is None: - return None - mon_list={} - for vnf in vnfs['vnfr:vnfr']: - if ns['id'] == vnf['nsr-id-ref']: - if 'monitoring-param' in vnf: - mon_list[vnf['name']] = vnf['monitoring-param'] - - return mon_list - - def list_key_pair(self): - data = BytesIO() - curl_cmd=self.get_curl_cmd('v1/api/config/key-pair?deep') - curl_cmd.setopt(pycurl.HTTPGET,1) - curl_cmd.setopt(pycurl.WRITEFUNCTION, data.write) - curl_cmd.perform() - curl_cmd.close() - resp = json.loads(data.getvalue().decode()) - if 'nsr:key-pair' in resp: - return resp['nsr:key-pair'] - return list() - - def list_ns_catalog(self): - resp = self.get_ns_catalog() - table=PrettyTable(['nsd name','id']) - for ns in resp['nsd:nsd']: - table.add_row([ns['name'],ns['id']]) - table.align='l' - print(table) - - def list_ns_instance(self): - resp = self.get_ns_instance_list() - table=PrettyTable(['ns instance name','id','catalog name','operational status','config status']) - if 'nsr' in resp: - for ns in resp['nsr']: - nsopdata=self.get_ns_opdata(ns['id']) - nsr=nsopdata['nsr:nsr'] - table.add_row([nsr['name-ref'],nsr['ns-instance-config-ref'],nsr['nsd-name-ref'],nsr['operational-status'],nsr['config-status']]) - table.align='l' - print(table) - - def get_nsd(self,name): - resp = self.get_ns_catalog() - for ns in resp['nsd:nsd']: - if name == ns['name']: - return ns - return None - - def get_vnfd(self,name): - resp = self.get_vnf_catalog() - for vnf in resp['vnfd:vnfd']: - if name == vnf['name']: - return vnf - return None - - def get_ns(self,name): - resp=self.get_ns_instance_list() - for ns in resp['nsr']: - if name == ns['name']: - return ns - return None - - def instantiate_ns(self,nsd_name,nsr_name,account,vim_network_prefix=None,ssh_keys=None,description='default description',admin_status='ENABLED'): - data = BytesIO() - curl_cmd=self.get_curl_cmd('api/config/ns-instance-config/nsr') - curl_cmd.setopt(pycurl.POST,1) - curl_cmd.setopt(pycurl.WRITEFUNCTION, data.write) - - postdata={} - postdata['nsr'] = list() - nsr={} - nsr['id']=str(uuid.uuid1()) - - nsd=self.get_nsd(nsd_name) - if nsd is None: - raise Exception('cannot find nsd {}'.format(nsd_name)) - - datacenter=self.get_datacenter(account) - if datacenter is None: - raise Exception('cannot find datacenter account {}'.format(account)) - - nsr['nsd']=nsd - nsr['name']=nsr_name - nsr['short-name']=nsr_name - nsr['description']=description - nsr['admin-status']=admin_status - nsr['om-datacenter']=datacenter['uuid'] - - if ssh_keys is not None: - # ssh_keys is comma separate list - ssh_keys_format=[] - for key in ssh_keys.split(','): - ssh_keys_format.append({'key-pair-ref':key}) - - nsr['ssh-authorized-key']=ssh_keys_format - - if vim_network_prefix is not None: - for index,vld in enumerate(nsr['nsd']['vld']): - network_name = vld['name'] - nsr['nsd']['vld'][index]['vim-network-name'] = '{}-{}'.format(vim_network_prefix,network_name) - - postdata['nsr'].append(nsr) - jsondata=json.dumps(postdata) - curl_cmd.setopt(pycurl.POSTFIELDS,jsondata) - curl_cmd.perform() - curl_cmd.close() - resp = json.loads(data.getvalue().decode()) - pprint.pprint(resp) - - def delete_nsd(self,nsd_name): - nsd=self.get_nsd(nsd_name) - if nsd is None: - raise Exception('cannot find nsd {}'.format(nsd_name)) - data = BytesIO() - curl_cmd=self.get_curl_cmd('api/running/nsd-catalog/nsd/'+nsd['id']) - curl_cmd.setopt(pycurl.CUSTOMREQUEST, "DELETE") - curl_cmd.setopt(pycurl.WRITEFUNCTION, data.write) - curl_cmd.perform() - curl_cmd.close() - resp = json.loads(data.getvalue().decode()) - pprint.pprint(resp) - - def delete_vnfd(self,vnfd_name): - vnfd=self.get_vnfd(vnfd_name) - if vnfd is None: - raise Exception('cannot find vnfd {}'.format(vnfd_name)) - data = BytesIO() - curl_cmd=self.get_curl_cmd('api/running/vnfd-catalog/vnfd/'+vnfd['id']) - curl_cmd.setopt(pycurl.CUSTOMREQUEST, "DELETE") - curl_cmd.setopt(pycurl.WRITEFUNCTION, data.write) - curl_cmd.perform() - curl_cmd.close() - resp = json.loads(data.getvalue().decode()) - pprint.pprint(resp) - - def terminate_ns(self,ns_name): - ns=self.get_ns(ns_name) - if ns is None: - raise Exception('cannot find ns {}'.format(ns_name)) - - data = BytesIO() - curl_cmd=self.get_curl_cmd('api/config/ns-instance-config/nsr/'+ns['id']) - curl_cmd.setopt(pycurl.CUSTOMREQUEST, "DELETE") - curl_cmd.setopt(pycurl.WRITEFUNCTION, data.write) - curl_cmd.perform() - curl_cmd.close() - resp = json.loads(data.getvalue().decode()) - pprint.pprint(resp) - - def upload_package(self,filename): - data = BytesIO() - curl_cmd=self.get_curl_upload_cmd(filename) - curl_cmd.setopt(pycurl.WRITEFUNCTION, data.write) - curl_cmd.perform() - curl_cmd.close() - resp = json.loads(data.getvalue().decode()) - pprint.pprint(resp) - - def add_vim_account(self,name,user_name,secret,auth_url,tenant,mgmt_network,float_ip_pool,account_type='openstack'): - data = BytesIO() - curl_cmd=self.get_curl_cmd('api/config/cloud') - curl_cmd.setopt(pycurl.POST,1) - curl_cmd.setopt(pycurl.WRITEFUNCTION, data.write) - vim_account={} - vim_account['account']={} - - vim_account['account']['name'] = name - vim_account['account']['account-type'] = account_type - vim_account['account'][account_type] = {} - vim_account['account'][account_type]['key'] = user_name - vim_account['account'][account_type]['secret'] = secret - vim_account['account'][account_type]['auth_url'] = auth_url - vim_account['account'][account_type]['tenant'] = tenant - vim_account['account'][account_type]['mgmt-network'] = mgmt_network - vim_account['account'][account_type]['floating-ip-pool'] = float_ip_pool - - jsondata=json.dumps(vim_account) - curl_cmd.setopt(pycurl.POSTFIELDS,jsondata) - curl_cmd.perform() - curl_cmd.close() - resp = json.loads(data.getvalue().decode()) - pprint.pprint(resp) - - def list_vim_accounts(self): - data = BytesIO() - curl_cmd=self.get_curl_cmd('v1/api/operational/datacenters') - curl_cmd.setopt(pycurl.HTTPGET,1) - curl_cmd.setopt(pycurl.WRITEFUNCTION, data.write) - curl_cmd.perform() - curl_cmd.close() - resp = json.loads(data.getvalue().decode()) - datacenters=resp['rw-launchpad:datacenters'] - if 'ro-accounts' in datacenters: - return datacenters['ro-accounts'] - return list() - - def get_datacenter(self,name): - data = BytesIO() - curl_cmd=self.get_curl_cmd('v1/api/operational/datacenters') - curl_cmd.setopt(pycurl.HTTPGET,1) - curl_cmd.setopt(pycurl.WRITEFUNCTION, data.write) - curl_cmd.perform() - curl_cmd.close() - resp = json.loads(data.getvalue().decode()) - datacenters=resp['rw-launchpad:datacenters'] - if 'ro-accounts' in datacenters: - for roaccount in datacenters['ro-accounts']: - if not 'datacenters' in roaccount: - continue - for datacenter in roaccount['datacenters']: - if datacenter['name'] == name: - return datacenter - return None - - def show_ns(self,ns_name): - resp = self.get_ns_instance_list() - table=PrettyTable(['attribute','value']) - - if 'nsr' in resp: - for ns in resp['nsr']: - if ns_name == ns['name']: - # dump ns config - for k,v in ns.items(): - table.add_row([k,json.dumps(v,indent=2)]) - - nsopdata=self.get_ns_opdata(ns['id']) - nsr_optdata=nsopdata['nsr:nsr'] - for k,v in nsr_optdata.items(): - table.add_row([k,json.dumps(v,indent=2)]) - table.align='l' - print(table) - - def show_ns_scaling(self,ns_name): - resp = self.get_ns_instance_list() - - table=PrettyTable(['instance-id','operational status','create-time','vnfr ids']) - - if 'nsr' in resp: - for ns in resp['nsr']: - if ns_name == ns['name']: - nsopdata=self.get_ns_opdata(ns['id']) - scaling_records=nsopdata['nsr:nsr']['scaling-group-record'] - for record in scaling_records: - if 'instance' in record: - instances=record['instance'] - for inst in instances: - table.add_row([inst['instance-id'],inst['op-status'],time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(inst['create-time'])),inst['vnfrs']]) - table.align='l' - print(table) - - def list_vnfr(self): - return self.get_vnfr_catalog() - - def list_vnf_catalog(self): - resp = self.get_vnf_catalog() - table=PrettyTable(['vnfd name','id']) - for ns in resp['vnfd:vnfd']: - table.add_row([ns['name'],ns['id']]) - table.align='l' - print(table) diff --git a/python-osmclient/osmclient/common/__init__.py b/python-osmclient/osmclient/common/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/python-osmclient/osmclient/scripts/__init__.py b/python-osmclient/osmclient/scripts/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/python-osmclient/osmclient/scripts/osm.py b/python-osmclient/osmclient/scripts/osm.py deleted file mode 100755 index d74580cfb..000000000 --- a/python-osmclient/osmclient/scripts/osm.py +++ /dev/null @@ -1,191 +0,0 @@ -import click -from osmclient.common import OsmAPI -from prettytable import PrettyTable -import pprint -import textwrap - -@click.group() -@click.option('--hostname',default=None,envvar='OSM_HOSTNAME',help='hostname of server. Also can set OSM_HOSTNAME in environment') -@click.pass_context -def cli(ctx,hostname): - if hostname is None: - print("either hostname option or OSM_HOSTNAME environment variable needs to be specified") - exit(1) - ctx.obj=OsmAPI.OsmAPI(hostname) - -@cli.command(name='ns-list') -@click.pass_context -def ns_list(ctx): - ctx.obj.list_ns_instance() - -@cli.command(name='nsd-list') -@click.pass_context -def nsd_list(ctx): - ctx.obj.list_ns_catalog() - -@cli.command(name='vnfd-list') -@click.pass_context -def vnfd_list(ctx): - ctx.obj.list_vnf_catalog() - -@cli.command(name='vnf-list') -@click.pass_context -def vnf_list(ctx): - resp=ctx.obj.list_vnfr() - table=PrettyTable(['vnf name','id','operational status','config Status','mgmt interface','nsr id']) - if resp is not None: - for vnfr in resp['vnfr:vnfr']: - if not 'mgmt-interface' in vnfr: - vnfr['mgmt-interface'] = {} - vnfr['mgmt-interface']['ip-address'] = None - table.add_row([vnfr['name'],vnfr['id'],vnfr['operational-status'],vnfr['config-status'],vnfr['mgmt-interface']['ip-address'],vnfr['nsr-id-ref']]) - table.align='l' - print(table) - -@cli.command(name='vnf-monitoring-show') -@click.argument('vnf_name') -@click.pass_context -def vnf_monitoring_show(ctx,vnf_name): - resp=ctx.obj.get_vnf_monitoring(vnf_name) - table=PrettyTable(['vnf name','monitoring name','value','units']) - if resp is not None: - for monitor in resp: - table.add_row([vnf_name,monitor['name'],monitor['value-integer'],monitor['units']]) - table.align='l' - print(table) - -@cli.command(name='ns-monitoring-show') -@click.argument('ns_name') -@click.pass_context -def ns_monitoring_show(ctx,ns_name): - resp=ctx.obj.get_ns_monitoring(ns_name) - table=PrettyTable(['vnf name','monitoring name','value','units']) - if resp is not None: - for key,val in resp.items(): - for monitor in val: - table.add_row([key,monitor['name'],monitor['value-integer'],monitor['units']]) - table.align='l' - print(table) - -@cli.command(name='ns-create') -@click.argument('nsd_name') -@click.argument('ns_name') -@click.argument('vim_account') -@click.option('--admin_status',default='ENABLED',help='administration status') -@click.option('--ssh_keys',default=None,help='comma separated list of keys to inject to vnfs') -@click.option('--vim_network_prefix',default=None,help='vim network name prefix') -@click.pass_context -def ns_create(ctx,nsd_name,ns_name,vim_account,admin_status,ssh_keys,vim_network_prefix): - ctx.obj.instantiate_ns(nsd_name,ns_name,vim_network_prefix=vim_network_prefix,ssh_keys=ssh_keys,account=vim_account) - -@cli.command(name='ns-delete') -@click.argument('ns_name') -@click.pass_context -def ns_delete(ctx,ns_name): - ctx.obj.terminate_ns(ns_name) - -''' -@cli.command(name='keypair-list') -@click.pass_context -def keypair_list(ctx): - resp=ctx.obj.list_key_pair() - table=PrettyTable(['key Name','key']) - for kp in resp: - table.add_row([kp['name'],kp['key']]) - table.align='l' - print(table) -''' - -@cli.command(name='upload-package') -@click.argument('filename') -@click.pass_context -def upload_package(ctx,filename): - ctx.obj.upload_package(filename) - -@cli.command(name='ns-show') -@click.argument('ns_name') -@click.pass_context -def ns_show(ctx,ns_name): - ctx.obj.show_ns(ns_name) - -@cli.command(name='ns-scaling-show') -@click.argument('ns_name') -@click.pass_context -def show_ns_scaling(ctx,ns_name): - ctx.obj.show_ns_scaling(ns_name) - -@cli.command(name='nsd-delete') -@click.argument('nsd_name') -@click.pass_context -def nsd_delete(ctx,nsd_name): - ctx.obj.delete_nsd(nsd_name) - -@cli.command(name='vnfd-delete') -@click.argument('vnfd_name') -@click.pass_context -def nsd_delete(ctx,vnfd_name): - ctx.obj.delete_vnfd(vnfd_name) - -@cli.command(name='config-agent-list') -@click.pass_context -def config_agent_list(ctx): - table=PrettyTable(['name','account-type','details']) - for account in ctx.obj.get_config_agents(): - table.add_row([account['name'],account['account-type'],account['juju']]) - table.align='l' - print(table) - -@cli.command(name='config-agent-delete') -@click.argument('name') -@click.pass_context -def config_agent_delete(ctx,name): - ctx.obj.delete_config_agent(name) - -@cli.command(name='config-agent-add') -@click.argument('name') -@click.argument('account_type') -@click.argument('server') -@click.argument('user') -@click.argument('secret') -@click.pass_context -def config_agent_add(ctx,name,account_type,server,user,secret): - ctx.obj.add_config_agent(name,account_type,server,user,secret) - -''' -@cli.command(name='vim-create') -@click.argument('name') -@click.argument('user') -@click.argument('password') -@click.argument('auth_url') -@click.argument('tenant') -@click.argument('mgmt_network') -@click.argument('floating_ip_pool') -@click.option('--account_type',default='openstack') -@click.pass_context -def vim_create(ctx,name,user,password,auth_url,tenant,mgmt_network,floating_ip_pool,account_type): - ctx.obj.add_vim_account(name,user,password,auth_url,tenant,mgmt_network,floating_ip_pool,account_type) -''' - -@cli.command(name='vim-list') -@click.pass_context -def vim_list(ctx): - resp=ctx.obj.list_vim_accounts() - table=PrettyTable(['ro-account','datacenter name','uuid']) - for roaccount in resp: - for datacenter in roaccount['datacenters']: - table.add_row([roaccount['name'],datacenter['name'],datacenter['uuid']]) - table.align='l' - print(table) - -@cli.command(name='vcs-list') -@click.pass_context -def vcs_list(ctx): - resp=ctx.obj.get_vcs_info() - table=PrettyTable(['component name','state']) - for component in resp: - table.add_row([component['component_name'],component['state']]) - table.align='l' - print(table) - -if __name__ == '__main__': - cli() diff --git a/python-osmclient/setup.py b/python-osmclient/setup.py deleted file mode 100644 index 13b1771a1..000000000 --- a/python-osmclient/setup.py +++ /dev/null @@ -1,17 +0,0 @@ -from setuptools import setup,find_packages - -setup( - name='osmclient', - version='0.1', - author='Mike Marchetti', - author_email='mmarchetti@sandvine.com', - packages=find_packages(), - include_package_data=True, - install_requires=[ - 'Click','prettytable' - ], - entry_points=''' - [console_scripts] - osm=osmclient.scripts.osm:cli - ''', -) diff --git a/python-osmclient/stdeb.cfg b/python-osmclient/stdeb.cfg deleted file mode 100644 index eadf0faf8..000000000 --- a/python-osmclient/stdeb.cfg +++ /dev/null @@ -1,2 +0,0 @@ -[DEFAULT] -Depends: python-setuptools, python-pycurl, python-click, python-prettytable diff --git a/skyquake/.storybook/config.js b/skyquake/.storybook/config.js index 42edf3cd4..d7168a91d 100644 --- a/skyquake/.storybook/config.js +++ b/skyquake/.storybook/config.js @@ -6,8 +6,12 @@ function loadStories() { // require('../tests/stories/sshKeyCard'); // require('../tests/stories/button'); // require('../tests/stories/sq-input-slider'); - require('../tests/stories/catalogCard'); + // require('../tests/stories/catalogCard'); + require('../tests/stories/FileManager'); + require('../tests/stories/inputs'); // require as many stories as you need. } configure(loadStories, module); + + diff --git a/skyquake/framework/core/api_utils/auth.js b/skyquake/framework/core/api_utils/auth.js new file mode 100644 index 000000000..7249ac71e --- /dev/null +++ b/skyquake/framework/core/api_utils/auth.js @@ -0,0 +1,145 @@ +/* + * + * Copyright 2017 RIFT.IO Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +/** + * Auth util for use across the api_server. + * @module framework/core/api_utils/auth + * @author Kiran Kashalkar + */ + +var jsonLoader = require('require-json'); +var passport = require('passport'); +var OpenIdConnectStrategy = require('passport-openidconnect').Strategy; +var BearerStrategy = require('passport-http-bearer').Strategy; +var OAuth2Strategy = require('passport-oauth2'); +var OAuth2RefreshTokenStrategy = require('passport-oauth2-middleware').Strategy; +var openidConnectConfig = require('./openidconnect_config.json'); +var _ = require('lodash'); +var constants = require('./constants'); +var utils = require('./utils'); +var request = utils.request; +var rp = require('request-promise'); +var nodeutil = require('util'); + + +var Authorization = function(openidConfig) { + + var self = this; + + self.passport = passport; + + self.openidConnectConfig = openidConnectConfig; + + var refreshStrategy = new OAuth2RefreshTokenStrategy({ + refreshWindow: constants.REFRESH_WINDOW, // Time in seconds to perform a token refresh before it expires + userProperty: 'user', // Active user property name to store OAuth tokens + authenticationURL: '/login', // URL to redirect unauthorized users to + callbackParameter: 'callback' //URL query parameter name to pass a return URL + }); + + self.passport.use('main', refreshStrategy); + + var openidConfigPrefix = openidConfig.idpServerProtocol + '://' + openidConfig.idpServerAddress + ':' + openidConfig.idpServerPortNumber; + + self.openidConnectConfig.authorizationURL = openidConfigPrefix + self.openidConnectConfig.authorizationURL; + self.openidConnectConfig.tokenURL = openidConfigPrefix + self.openidConnectConfig.tokenURL; + self.openidConnectConfig.callbackURL = openidConfig.callbackServerProtocol + '://' + openidConfig.callbackAddress + ':' + openidConfig.callbackPortNumber + self.openidConnectConfig.callbackURL; + + var userInfoURL = openidConfigPrefix + self.openidConnectConfig.userInfoURL; + + function SkyquakeOAuth2Strategy(options, verify) { + OAuth2Strategy.call(this, options, verify); + } + nodeutil.inherits(SkyquakeOAuth2Strategy, OAuth2Strategy); + + SkyquakeOAuth2Strategy.prototype.userProfile = function(access_token, done) { + + var requestHeaders = { + 'Authorization': 'Bearer ' + access_token + }; + + request({ + url: userInfoURL, + type: 'GET', + headers: requestHeaders, + forever: constants.FOREVER_ON, + rejectUnauthorized: constants.REJECT_UNAUTHORIZED + }, function(err, response, body) { + if (err) { + console.log('Error obtaining userinfo: ', err); + return done(null, { + username: '', + subject: '' + }); + } else { + if (response.statusCode == constants.HTTP_RESPONSE_CODES.SUCCESS.OK) { + try { + var data = JSON.parse(response.body); + var username = data['preferred_username']; + var subject = data['sub']; + var domain = data['user_domain'] || 'system'; + return done(null, { + username: username, + subject: subject, + domain: domain + }) + } catch (ex) { + console.log('Error parsing userinfo data'); + return done(null, { + username: '', + subject: '' + }); + } + } + } + }) + }; + + var oauthStrategy = new SkyquakeOAuth2Strategy(self.openidConnectConfig, + refreshStrategy.getOAuth2StrategyCallback()); + + self.passport.use('oauth2', oauthStrategy); + refreshStrategy.useOAuth2Strategy(oauthStrategy); + + self.passport.serializeUser(function(user, done) { + done(null, user); + }); + + self.passport.deserializeUser(function(obj, done) { + done(null, obj); + }); + +}; + +Authorization.prototype.configure = function(config) { + this.config = config; + // Initialize Passport and restore authentication state, if any, from the + // session. + if (this.config.app) { + this.config.app.use(this.passport.initialize()); + this.config.app.use(this.passport.session()); + } else { + console.log('FATAL error. Bad config passed into authorization module'); + } +}; + +Authorization.prototype.invalidate_token = function(token) { + +}; + +module.exports = Authorization; diff --git a/skyquake/framework/core/api_utils/constants.js b/skyquake/framework/core/api_utils/constants.js index 0aac7d2d0..b27e7da30 100644 --- a/skyquake/framework/core/api_utils/constants.js +++ b/skyquake/framework/core/api_utils/constants.js @@ -73,11 +73,21 @@ constants.SOCKET_BASE_PORT = 3500; constants.SOCKET_POOL_LENGTH = 20; constants.SERVER_PORT = process.env.SERVER_PORT || 8000; constants.SECURE_SERVER_PORT = process.env.SECURE_SERVER_PORT || 8443; +constants.REJECT_UNAUTHORIZED = false; -constants.BASE_PACKAGE_UPLOAD_DESTINATION = 'upload/packages/'; +constants.BASE_PACKAGE_UPLOAD_DESTINATION = 'upload'; constants.PACKAGE_MANAGER_SERVER_PORT = 4567; constants.PACKAGE_FILE_DELETE_DELAY_MILLISECONDS = 3 * 1000 * 60; //5 minutes constants.PACKAGE_FILE_ONBOARD_TRANSACTION_STATUS_CHECK_DELAY_MILLISECONDS = 2 * 1000; //2 seconds +constants.REFRESH_WINDOW = 10; //Time in seconds to perform a token refresh before it expires +constants.LAUNCHPAD_ADDRESS = 'localhost'; +constants.LAUNCHPAD_PORT = 8008; +constants.IDP_SERVER_PROTOCOL = 'https'; +constants.IDP_PORT_NUMBER = 8009; +constants.CALLBACK_SERVER_PROTOCOL = 'https'; +constants.CALLBACK_PORT_NUMBER = 8443; +constants.CALLBACK_ADDRESS = 'localhost'; +constants.END_SESSION_PATH = 'end_session'; module.exports = constants; \ No newline at end of file diff --git a/skyquake/framework/core/api_utils/csrf.js b/skyquake/framework/core/api_utils/csrf.js new file mode 100644 index 000000000..62855f27a --- /dev/null +++ b/skyquake/framework/core/api_utils/csrf.js @@ -0,0 +1,67 @@ +/* + * + * Copyright 2017 RIFT.IO Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +/** + * CSRF util for use across the api_server. + * @module framework/core/api_utils/csrf + * @author Kiran Kashalkar + */ + +var constants = require('./constants.js'); +var utils = require('./utils.js'); + +var target = null; + +function configure(config) { + target = config.target; +} + +function csrfCheck(req, res, next) { + var host = null; + + if (req.headers.origin != 'null') { + host = utils.getHostNameFromURL(req.headers.origin); + } else if (req.headers.referer) { + host = utils.getHostNameFromURL(req.headers.referer); + } else { + var msg = 'Request did not contain an origin or referer header. Request terminated.'; + var error = {}; + error.statusCode = constants.HTTP_RESPONSE_CODES.ERROR.METHOD_NOT_ALLOWED; + error.errorMessage = { + error: msg + } + return utils.sendErrorResponse(error, res); + } + + if (!host || host != target) { + var msg = 'Request did not originate from authorized source (Potential CSRF attempt). Request terminated.'; + var error = {}; + error.statusCode = constants.HTTP_RESPONSE_CODES.ERROR.METHOD_NOT_ALLOWED; + error.errorMessage = { + error: msg + } + return utils.sendErrorResponse(error, res); + } else { + return next(); + } +} + +module.exports = { + configure: configure, + csrfCheck: csrfCheck +}; \ No newline at end of file diff --git a/skyquake/framework/core/api_utils/openidconnect_config.json b/skyquake/framework/core/api_utils/openidconnect_config.json new file mode 100644 index 000000000..63c4a1d31 --- /dev/null +++ b/skyquake/framework/core/api_utils/openidconnect_config.json @@ -0,0 +1,9 @@ +{ + "scope": "openid", + "clientID": "cncudWkub3BlbmlkY2xpZW50", + "clientSecret": "riftiorocks", + "authorizationURL": "/authorization", + "tokenURL": "/token", + "userInfoURL": "/userinfo", + "callbackURL": "/callback" +} \ No newline at end of file diff --git a/skyquake/framework/core/api_utils/sockets.js b/skyquake/framework/core/api_utils/sockets.js index 5e0b25bfb..a86f5ed35 100644 --- a/skyquake/framework/core/api_utils/sockets.js +++ b/skyquake/framework/core/api_utils/sockets.js @@ -1,5 +1,5 @@ /* - * + * * Copyright 2016 RIFT.IO Inc * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -33,6 +33,7 @@ var url = require('url'); var sockjs = require('sockjs'); var websocket_multiplex = require('websocket-multiplex'); var utils = require('./utils.js'); +var configurationAPI = require('../modules/api/configuration.js'); var Subscriptions = function() { @@ -182,7 +183,7 @@ Subscriptions.prototype.socketInstance = function(url, req, wss, Type, channelId if (Type == PollingSocket) { Socket = new Type(url, req, 1000, req.body); } else { - Socket = new Type(url); + Socket = new Type(url, ['Bearer', req.session.passport.user.user['access_token']]); } console.log('Socket assigned for url', url); } @@ -278,12 +279,12 @@ function PollingSocket(url, req, interval, config) { self.isClosed = false; var requestHeaders = {}; _.extend(requestHeaders, { - 'Authorization': req.get('Authorization') + Cookie: req.get('Cookie') }); var pollServer = function() { Request({ - url: url, + url: utils.projectContextUrl(req, url), method: config.method || 'GET', headers: requestHeaders, json: config.payload, @@ -294,7 +295,11 @@ function PollingSocket(url, req, interval, config) { console.log('Error polling: ' + url); } else { if (!self.isClosed) { - self.poll = setTimeout(pollServer, 1000 || interval); + if(process.env.DISABLE_POLLING != "TRUE") { + self.poll = setTimeout(pollServer, 1000 || interval); + } else { + console.log('Polling is disabled. Finishing request.') + } var data = response.body; if (self.onmessage) { self.onmessage(data); diff --git a/skyquake/framework/core/api_utils/utils.js b/skyquake/framework/core/api_utils/utils.js index 5b17279d5..cdf12fc91 100644 --- a/skyquake/framework/core/api_utils/utils.js +++ b/skyquake/framework/core/api_utils/utils.js @@ -49,6 +49,41 @@ var confdPort = function(api_server) { return api_server + ':' + CONFD_PORT; }; +var projectContextUrl = function(req, url) { + //NOTE: We need to go into the sessionStore because express-session + // does not reliably update the session. + // See https://github.com/expressjs/session/issues/450 + var projectId = (req.session && + req.sessionStore && + req.sessionStore.sessions && + req.sessionStore.sessions[req.session.id] && + JSON.parse(req.sessionStore.sessions[req.session.id])['projectId']) || + (null); + if (projectId) { + projectId = encodeURIComponent(projectId); + return url.replace(/(\/api\/operational\/|\/api\/config\/)(.*)/, '$1project/' + projectId + '/$2'); + } + return url; +} + +var addProjectContextToRPCPayload = function(req, url, inputPayload) { + //NOTE: We need to go into the sessionStore because express-session + // does not reliably update the session. + // See https://github.com/expressjs/session/issues/450 + var projectId = (req.session && + req.sessionStore && + req.sessionStore.sessions && + req.sessionStore.sessions[req.session.id] && + JSON.parse(req.sessionStore.sessions[req.session.id])['projectId']) || + (null); + if (projectId) { + if (url.indexOf('/api/operations/')) { + inputPayload['project-name'] = projectId; + } + } + return inputPayload; +} + var validateResponse = function(callerName, error, response, body, resolve, reject) { var res = {}; @@ -61,12 +96,12 @@ var validateResponse = function(callerName, error, response, body, resolve, reje }; reject(res); return false; - } else if (response.statusCode >= 400) { + } else if (response.statusCode >= CONSTANTS.HTTP_RESPONSE_CODES.ERROR.BAD_REQUEST) { console.log('Problem with "', callerName, '": ', response.statusCode, ':', body); res.statusCode = response.statusCode; // auth specific - if (response.statusCode == 401) { + if (response.statusCode == CONSTANTS.HTTP_RESPONSE_CODES.ERROR.UNAUTHORIZED) { res.errorMessage = { error: 'Authentication needed' + body }; @@ -81,7 +116,7 @@ var validateResponse = function(callerName, error, response, body, resolve, reje reject(res); return false; - } else if (response.statusCode == 204) { + } else if (response.statusCode == CONSTANTS.HTTP_RESPONSE_CODES.SUCCESS.NO_CONTENT) { resolve({ statusCode: response.statusCode, data: {} @@ -95,7 +130,7 @@ var validateResponse = function(callerName, error, response, body, resolve, reje var checkAuthorizationHeader = function(req) { return new Promise(function(resolve, reject) { - if (req.get('Authorization') == null) { + if (req.session && req.session.authorization == null) { reject(); } else { resolve(); @@ -119,12 +154,12 @@ if (process.env.LOG_REQUESTS) { reject(res); fs.appendFileSync(logFile, 'Request API: ' + response.request.uri.href + ' ; ' + 'Error: ' + error); return false; - } else if (response.statusCode >= 400) { + } else if (response.statusCode >= CONSTANTS.HTTP_RESPONSE_CODES.ERROR.BAD_REQUEST) { console.log('Problem with "', callerName, '": ', response.statusCode, ':', body); res.statusCode = response.statusCode; // auth specific - if (response.statusCode == 401) { + if (response.statusCode == CONSTANTS.HTTP_RESPONSE_CODES.ERROR.UNAUTHORIZED) { res.errorMessage = { error: 'Authentication needed' + body }; @@ -140,7 +175,7 @@ if (process.env.LOG_REQUESTS) { reject(res); fs.appendFileSync(logFile, 'Request API: ' + response.request.uri.href + ' ; ' + 'Error Body: ' + body); return false; - } else if (response.statusCode == 204) { + } else if (response.statusCode == CONSTANTS.HTTP_RESPONSE_CODES.SUCCESS.NO_CONTENT) { resolve(); fs.appendFileSync(logFile, 'Request API: ' + response.request.uri.href + ' ; ' + 'Response Body: ' + body); return false; @@ -162,6 +197,9 @@ if (process.env.LOG_REQUESTS) { * @param {Function} res - a handle to the express response function */ var sendErrorResponse = function(error, res) { + if (!error.statusCode) { + console.error('Status Code has not been set in error object: ', error); + } res.status(error.statusCode); res.send(error); } @@ -197,10 +235,10 @@ var passThroughConstructor = function(app) { } new Promise(function(resolve, reject) { request({ - uri: uri, + uri: projectContextUrl(req, uri), method: 'GET', headers: _.extend({}, CONSTANTS.HTTP_HEADERS.accept[type], { - 'Authorization': req.get('Authorization'), + 'Authorization': req.session && req.session.authorization, forever: CONSTANTS.FOREVER_ON, rejectUnauthorized: false, }) @@ -226,6 +264,31 @@ var getPortForProtocol = function(protocol) { } } +var buildRedirectURL = function(req, globalConfiguration, plugin, extra) { + var api_server = req.query['api_server'] || (req.protocol + '://' + globalConfiguration.get().api_server); + var download_server = req.query['dev_download_server'] || globalConfiguration.get().dev_download_server; + var url = '/'; + url += plugin; + url += '/?api_server=' + api_server; + url += download_server ? '&dev_download_server=' + download_server : ''; + url += extra || ''; + return url; +} + +var getHostNameFromURL = function(url) { + var match = url.match(/^(https?\:)\/\/(([^:\/?#]*)(?:\:([0-9]+))?)([^?#]*)(\?[^#]*|)(#.*|)$/); + return match && match[3]; +} + +var dataToJsonSansPropNameNamespace = function(s) { + var a = JSON.parse(s); + var b = JSON.stringify(a); + var c = b.replace(/{"[-\w]+:/g, '{"'); + var d = c.replace(/,"[-\w]+:/g, ',"'); + var j = JSON.parse(d); + return j; +} + module.exports = { /** * Ensure confd port is on api_server variable. @@ -244,5 +307,15 @@ module.exports = { passThroughConstructor: passThroughConstructor, - getPortForProtocol: getPortForProtocol + getPortForProtocol: getPortForProtocol, + + projectContextUrl: projectContextUrl, + + addProjectContextToRPCPayload: addProjectContextToRPCPayload, + + buildRedirectURL: buildRedirectURL, + + getHostNameFromURL: getHostNameFromURL, + + dataToJsonSansPropNameNamespace: dataToJsonSansPropNameNamespace }; diff --git a/skyquake/framework/core/modules/api/appConfigAPI.js b/skyquake/framework/core/modules/api/appConfigAPI.js new file mode 100644 index 000000000..ce93bdc4c --- /dev/null +++ b/skyquake/framework/core/modules/api/appConfigAPI.js @@ -0,0 +1,119 @@ +/* + * + * Copyright 2016 RIFT.IO Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +// DescriptorModelMeta API (NSD + VNFD) + + +var Schema = {}; +var request = require('request'); +var Promise = require('promise'); +var constants = require('../../api_utils/constants'); +var utils = require('../../api_utils/utils'); +var _ = require('lodash'); +var cors = require('cors'); +var bodyParser = require('body-parser'); +var utils = require('../../api_utils/utils'); +var sessionAPI = require('./sessions.js'); +var configuration = require('./configuration'); + +var router = require('express').Router(); + +router.use(bodyParser.json()); +router.use(cors()); +router.use(bodyParser.urlencoded({ + extended: true +})); + +router.get('/app-config', cors(), function (req, res) { + getConfig(req).then(function (response) { + utils.sendSuccessResponse(response, res); + }, function (error) { + utils.sendErrorResponse(error, res); + }); +}); + +var inactivityTimeout = process.env.UI_TIMEOUT_SECS || 600000; + +var versionPromise = null; + +var init = function () { + versionPromise = new Promise( + function (resolve, reject) { + sessionAPI.sessionPromise.then( + function (session) { + request({ + url: configuration.getBackendURL() + '/api/operational/version', + type: 'GET', + headers: _.extend({}, constants.HTTP_HEADERS.accept.data, { + 'Authorization': session.authorization + }), + forever: constants.FOREVER_ON, + rejectUnauthorized: false + }, + function (error, response, body) { + var data; + if (utils.validateResponse('schema/version.get', error, response, body, resolve, reject)) { + try { + data = JSON.parse(response.body)['rw-base:version']; + resolve(data.version); + } catch (e) { + return reject({}); + } + } else { + console.log(error); + } + }); + }); + }); +} + +var getConfig = function (req) { + var api_server = req.query['api_server']; + + var requests = [versionPromise]; + + return new Promise(function (resolve, reject) { + Promise.all(requests).then( + function (results) { + var data = { + version: results[0], + 'api-server': configuration.getBackendURL, + 'inactivity-timeout': process.env.UI_TIMEOUT_SECS || 600000 + } + resolve({ + data: data, + statusCode: constants.HTTP_RESPONSE_CODES.SUCCESS.OK + }); + }).catch( + function (error) { + var response = {}; + console.log('Problem with config.get', error); + response.statusCode = error.statusCode || 500; + response.errorMessage = { + error: 'Failed to get config' + error + }; + reject(response); + }); + }); +}; + +module.exports = { + getRouter: function () { + return router; + }, + init: init +}; \ No newline at end of file diff --git a/skyquake/framework/core/modules/api/configuration.js b/skyquake/framework/core/modules/api/configuration.js index 376264396..d1fca27ee 100644 --- a/skyquake/framework/core/modules/api/configuration.js +++ b/skyquake/framework/core/modules/api/configuration.js @@ -1,5 +1,5 @@ /* - * + * * Copyright 2016 RIFT.IO Inc * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -30,7 +30,11 @@ var configurationAPI = {}; var _ = require('lodash'); var GLOBAL_CONFIGURATION = { api_server: 'localhost', - ssl_enabled: true + ssl_enabled: true, + api_server_port_number: constants.SECURE_SERVER_PORT, + idp_server_address: constants.LAUNCHPAD_ADDRESS, + idp_server_protocol: constants.IDP_SERVER_PROTOCOL, + idp_server_port_number: constants.IDP_PORT_NUMBER }; /** @@ -92,4 +96,18 @@ configurationAPI.globalConfiguration.get = function() { return GLOBAL_CONFIGURATION; }; +var backendURL = null; +configurationAPI.getBackendURL = function () { + if (!backendURL) { + backendURL = GLOBAL_CONFIGURATION.api_server_protocol + '://' + GLOBAL_CONFIGURATION.api_server + ':' + GLOBAL_CONFIGURATION.api_server_port_number; + } + return backendURL; +} + +configurationAPI.getBackendAPI = function () { + return configurationAPI.getBackendURL() + '/v2/api'; +} + + + module.exports = configurationAPI; diff --git a/skyquake/framework/core/modules/api/descriptorModelMetaAPI.js b/skyquake/framework/core/modules/api/descriptorModelMetaAPI.js index b0223b2ab..34d30b36e 100644 --- a/skyquake/framework/core/modules/api/descriptorModelMetaAPI.js +++ b/skyquake/framework/core/modules/api/descriptorModelMetaAPI.js @@ -36,7 +36,7 @@ ModelMeta.get = function(req) { uri: utils.confdPort(api_server) + '/api/schema/nsd-catalog/nsd', method: 'GET', headers: _.extend({}, constants.HTTP_HEADERS.accept.collection, { - 'Authorization': req.get('Authorization') + 'Authorization': req.session && req.session.authorization }), forever: constants.FOREVER_ON, rejectUnauthorized: false, @@ -46,7 +46,7 @@ ModelMeta.get = function(req) { uri: utils.confdPort(api_server) + '/api/schema/vnfd-catalog/vnfd', method: 'GET', headers: _.extend({}, constants.HTTP_HEADERS.accept.collection, { - 'Authorization': req.get('Authorization') + 'Authorization': req.session && req.session.authorization }), forever: constants.FOREVER_ON, rejectUnauthorized: false, diff --git a/skyquake/framework/core/modules/api/modelAPI.js b/skyquake/framework/core/modules/api/modelAPI.js new file mode 100644 index 000000000..1f14cb798 --- /dev/null +++ b/skyquake/framework/core/modules/api/modelAPI.js @@ -0,0 +1,359 @@ +/* + * + * Copyright 2016 RIFT.IO Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +// DescriptorModelMeta API (NSD + VNFD) + + +var Schema = {}; +var rp = require('request-promise'); +var Promise = require('promise'); +var constants = require('../../api_utils/constants'); +var utils = require('../../api_utils/utils'); +var _ = require('lodash'); +var cors = require('cors'); +var bodyParser = require('body-parser'); +var utils = require('../../api_utils/utils'); +var configuration = require('./configuration'); + +var router = require('express').Router(); + + +router.use(bodyParser.json()); +router.use(cors()); +router.use(bodyParser.urlencoded({ + extended: true +})); + +router.get('/model', cors(), function (req, res) { + get(req).then(function (response) { + utils.sendSuccessResponse(response, res); + }, function (error) { + utils.sendErrorResponse(error, res); + }); +}); + +router.patch('/model', cors(), function (req, res) { + update(req).then(function (response) { + utils.sendSuccessResponse(response, res); + }, function (error) { + utils.sendErrorResponse(error, res); + }); +}); + +router.put('/model', cors(), function (req, res) { + add(req).then(function (response) { + utils.sendSuccessResponse(response, res); + }, function (error) { + utils.sendErrorResponse(error, res); + }); +}); + +router.delete('/model', cors(), function (req, res) { + remove(req).then(function (response) { + utils.sendSuccessResponse(response, res); + }, function (error) { + utils.sendErrorResponse(error, res); + }); +}); + +module.exports = { + getRouter: function () { + return router; + }, + init: function () {} +}; + +get = function (req) { + var backend = configuration.getBackendAPI(); + var modelPath = req.query['path']; + var requestHeaders = _.extend({}, constants.HTTP_HEADERS.accept.collection, { + 'Authorization': req.session && req.session.authorization + }) + return new Promise(function (resolve, reject) { + Promise.all([ + rp({ + uri: backend + '/config' + modelPath, + method: 'GET', + headers: requestHeaders, + forever: constants.FOREVER_ON, + rejectUnauthorized: false, + resolveWithFullResponse: true + // }), + // rp({ + // uri: utils.projectContextUrl(req, backend + '/api/operational' + modelPath), + // method: 'GET', + // headers: requestHeaders, + // forever: constants.FOREVER_ON, + // rejectUnauthorized: false, + // resolveWithFullResponse: true + }) + ]).then(function (results) { + var response = { + statusCode: results[0].statusCode || 200, + data: [null] + } + if (results[0].body && !results[0].error) { + var result = JSON.parse(results[0].body); + if (result.collection) { + result = result.collection[Object.keys(result.collection)[0]]; + if (!result.length) { + result = null; + } else if (result.length === 1) { + result = result[0]; + } + } + response.data = result; + } + resolve(response); + + }).catch(function (error) { + var res = {}; + console.log('Problem with model get', error); + res.statusCode = error.statusCode || 500; + res.errorMessage = { + error: 'Failed to get model: ' + error + }; + reject(res); + }); + }); +}; + +add = function (req) { + var backend = configuration.getBackendAPI(); + var modelPath = req.query['path']; + var targetProperty = modelPath.split('/').pop(); + var newElement = {}; + var data = {}; + console.log(req.body); + _.forIn(req.body, function (value, key) { + if (_.isObject(value)) { + if (value.type === 'leaf_empty') { + if (value.data) { + data[key] = ' '; + } + } else if (value.type === 'leaf_list') { + data[key] = value.data.add; + } + } else { + data[key] = value; + } + }); + newElement[targetProperty] = [data]; + console.log(newElement); + var target = backend + '/config' + modelPath; + var method = 'POST' + var requestHeaders = _.extend({}, + constants.HTTP_HEADERS.accept.data, { + 'Authorization': req.session && req.session.authorization + }); + return new Promise(function (resolve, reject) { + rp({ + uri: target, + method: method, + headers: requestHeaders, + forever: constants.FOREVER_ON, + json: newElement, + rejectUnauthorized: false, + resolveWithFullResponse: true + }).then(function (results) { + var response = {}; + response.data = { + path: modelPath, + data: data + }; + response.statusCode = constants.HTTP_RESPONSE_CODES.SUCCESS.OK; + console.log(response); + resolve(response); + }).catch(function (result) { + var response = {}; + var error = {}; + if (result.error['rpc-reply']) { + error.type = result.error['rpc-reply']['rpc-error']['error-tag']; + error.message = result.error['rpc-reply']['rpc-error']['error-message']; + error.rpcError = result.error['rpc-reply']['rpc-error'] + } else { + error.type = 'api-error'; + error.message = 'invalid api call'; + } + console.log('Problem with model update', error); + response.statusCode = error.statusCode || 500; + response.error = error; + reject(response); + }); + }); +}; + +update = function (req) { + var backend = configuration.getBackendAPI(); + var modelPath = req.query['path']; + var requestHeaders = _.extend({}, + constants.HTTP_HEADERS.accept.data, { + 'Authorization': req.session && req.session.authorization + }); + var base = backend + '/config' + modelPath + '/'; + + function getUpdatePromise(name, value) { + var data = {}; + data[name] = value; + return new Promise(function (resolve, reject) { + rp({ + uri: base + name, + method: value ? 'PATCH' : 'DELETE', + headers: requestHeaders, + forever: constants.FOREVER_ON, + json: data, + rejectUnauthorized: false, + resolveWithFullResponse: true + }).then(function (result) { + resolve({ + element: name, + success: true, + value: value + }); + }).catch(function (result) { + var error = {}; + if (result.error['rpc-reply']) { + error.type = result.error['rpc-reply']['rpc-error']['error-tag']; + error.message = result.error['rpc-reply']['rpc-error']['error-message']; + error.rpcError = result.error['rpc-reply']['rpc-error'] + } else { + error.type = 'api-error'; + error.message = 'invalid api call'; + } + resolve({ + element: name, + success: false, + error: error, + value: value + }); + }) + }) + } + + function getDeletePromise(targetProp, item) { + if (item) { + targetProp = targetProp + '/' + item; + } + return getUpdatePromise(targetProp, ''); + } + + var updates = []; + _.forIn(req.body, function (value, key) { + var data = {}; + if (_.isObject(value)) { + if (value.type === 'leaf_list') { + _.forEach(value.data.remove, function (v) { + updates.push(getDeletePromise(key)) + }) + _.forEach(value.data.add, function (v) { + updates.push(getUpdatePromise(key, v)) + }) + } else if (value.type === 'leaf_empty') { + if (value.data) { + updates.push(getUpdatePromise(key, ' ')) + } else { + updates.push(getDeletePromise(key)) + } + } + } else { + updates.push(getUpdatePromise(key, value)) + } + }) + + return new Promise(function (resolve, reject) { + Promise.all(updates).then(function (results) { + var response = {}; + var output = {}; + var hasError = false; + _.forEach(results, function (result) { + var record = {}; + if (output[result.element]) { + if (_.isArray(output[result.element].value)) { + output[result.element].value.push(result.value); + } else { + output[result.element].value = [output[result.element].value, result.value]; + } + } else { + output[result.element] = result; + } + hasError = hasError || !result.success + }) + response.data = { + result: output, + hasError: hasError + }; + response.statusCode = constants.HTTP_RESPONSE_CODES.SUCCESS.OK; + console.log(response); + resolve(response); + }).catch(function (result) { + var response = {}; + var error = {}; + if (result.error['rpc-reply']) { + error.type = result.error['rpc-reply']['rpc-error']['error-tag']; + error.message = result.error['rpc-reply']['rpc-error']['error-message']; + error.rpcError = result.error['rpc-reply']['rpc-error'] + } else { + error.type = 'api-error'; + error.message = 'invalid api call'; + } + console.log('Problem with model update', error); + response.statusCode = error.statusCode || 500; + response.error = error; + reject(response); + }); + }); +}; + +remove = function (req) { + var backend = configuration.getBackendAPI(); + var modelPath = req.query['path']; + var target = backend + '/config' + modelPath; + var requestHeaders = _.extend({}, + constants.HTTP_HEADERS.accept.data, + constants.HTTP_HEADERS.content_type.data, { + 'Authorization': req.session && req.session.authorization + }) + return new Promise(function (resolve, reject) { + rp({ + url: target, + method: 'DELETE', + headers: requestHeaders, + forever: constants.FOREVER_ON, + rejectUnauthorized: false, + }).then(function (response) { + return resolve({ + statusCode: constants.HTTP_RESPONSE_CODES.SUCCESS.OK, + data: modelPath + }); + }).catch(function (result) { + var response = {}; + var error = {}; + if (result.error['rpc-reply']) { + error.type = result.error['rpc-reply']['rpc-error']['error-tag']; + error.message = result.error['rpc-reply']['rpc-error']['error-message']; + error.rpcError = result.error['rpc-reply']['rpc-error'] + } else { + error.type = 'api-error'; + error.message = 'invalid api call'; + } + console.log('Problem with model update', error); + response.statusCode = error.statusCode || 500; + response.error = error; + reject(response); + }); + }) +} \ No newline at end of file diff --git a/skyquake/framework/core/modules/api/projectManagementAPI.js b/skyquake/framework/core/modules/api/projectManagementAPI.js new file mode 100644 index 000000000..d65e8905a --- /dev/null +++ b/skyquake/framework/core/modules/api/projectManagementAPI.js @@ -0,0 +1,297 @@ +/* + * + * Copyright 2016 RIFT.IO Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +// DescriptorModelMeta API (NSD + VNFD) + + +var ProjectManagement = {}; +var Promise = require('bluebird'); +var rp = require('request-promise'); +var Promise = require('promise'); +var constants = require('../../api_utils/constants'); +var utils = require('../../api_utils/utils'); +var _ = require('lodash'); +var API_VERSION = 'v2'; +ProjectManagement.get = function(req, fields) { + var self = this; + var api_server = req.query['api_server']; + // by default just load basic info as this request is expensive + fields = fields || ['name', 'description', 'project-config']; + var select = fields.length ? '?fields=' + fields.join(';') : ''; + + return new Promise(function(resolve, reject) { + Promise.all([ + rp({ + uri: `${utils.confdPort(api_server)}/${API_VERSION}/api/operational/project` + select, + method: 'GET', + headers: _.extend({}, constants.HTTP_HEADERS.accept.data, { + 'Authorization': req.session && req.session.authorization + }), + forever: constants.FOREVER_ON, + rejectUnauthorized: false, + resolveWithFullResponse: true + }) + ]).then(function(result) { + var response = {}; + response['data'] = {}; + if (result[0].body) { + response['data']['project'] = JSON.parse(result[0].body)['rw-project:project']; + } + response.statusCode = constants.HTTP_RESPONSE_CODES.SUCCESS.OK + + resolve(response); + }).catch(function(error) { + var response = {}; + console.log('Problem with ProjectManagement.get', error); + response.statusCode = error.statusCode || 500; + response.errorMessage = { + error: 'Failed to get ProjectManagement' + error + }; + reject(response); + }); + }); +}; + +ProjectManagement.create = function(req) { + var self = this; + var api_server = req.query['api_server']; + var data = req.body; + data = { + "project":[data] + } + return new Promise(function(resolve, reject) { + Promise.all([ + rp({ + uri: utils.confdPort(api_server) + '/' + API_VERSION + '/api/config/project', + method: 'POST', + headers: _.extend({}, constants.HTTP_HEADERS.accept.data, { + 'Authorization': req.session && req.session.authorization + }), + forever: constants.FOREVER_ON, + json: data, + rejectUnauthorized: false, + resolveWithFullResponse: true + }) + ]).then(function(result) { + var response = {}; + response['data'] = {}; + if (result[0].body) { + response['data'] = result[0].body; + } + response.statusCode = constants.HTTP_RESPONSE_CODES.SUCCESS.OK + + resolve(response); + }).catch(function(error) { + var response = {}; + console.log('Problem with ProjectManagement.create', error); + response.statusCode = error.statusCode || 500; + response.errorMessage = { + error: 'Failed to create user' + error + }; + reject(response); + }); + }); +}; +ProjectManagement.update = function(req) { + //"rw-project:project" + var self = this; + var api_server = req.query['api_server']; + var bodyData = req.body; + // oddly enough, if we do not encode this here letting the request below does so incorrectly + var projectName = encodeURIComponent(bodyData.name); + var descriptionData = { + "rw-project:project" : { + "name": bodyData.name, + "description": bodyData.description + } + } + var updateTasks = []; + var baseUrl = utils.confdPort(api_server) + '/' + API_VERSION + '/api/config/project/' + projectName + var updateProjectConfig = rp({ + uri: baseUrl + '/project-config', + method: 'PUT', + headers: _.extend({}, constants.HTTP_HEADERS.accept.data, { + 'Authorization': req.session && req.session.authorization + }), + forever: constants.FOREVER_ON, + json: { + "project-config": bodyData['project-config'] + }, + rejectUnauthorized: false, + resolveWithFullResponse: true + }); + updateTasks.push(updateProjectConfig); + + var updateProjectDescription = rp({ + uri: baseUrl + '/description', + method: 'PATCH', + headers: _.extend({}, constants.HTTP_HEADERS.accept.data, { + 'Authorization': req.session && req.session.authorization + }), + forever: constants.FOREVER_ON, + json: {"description": bodyData.description}, + rejectUnauthorized: false, + resolveWithFullResponse: true + }); + updateTasks.push(updateProjectDescription) + return new Promise(function(resolve, reject) { + Promise.all( + updateTasks + ).then(function(result) { + var response = {}; + response['data'] = {}; + if (result[0].body) { + response['data'] = result[0].body; + } + response.statusCode = constants.HTTP_RESPONSE_CODES.SUCCESS.OK + + resolve(response); + }).catch(function(error) { + var response = {}; + console.log('Problem with ProjectManagement.update', error); + response.statusCode = error.statusCode || 500; + response.errorMessage = { + error: 'Failed to update project - ' + error + }; + reject(response); + }); + }); +}; + +ProjectManagement.delete = function(req) { + var self = this; + var projectname = encodeURIComponent(req.params.projectname); + var api_server = req.query["api_server"]; + var requestHeaders = {}; + var url = utils.confdPort(api_server) + '/' + API_VERSION + '/api/config/project/' + projectname + return new Promise(function(resolve, reject) { + _.extend(requestHeaders, + constants.HTTP_HEADERS.accept.data, + constants.HTTP_HEADERS.content_type.data, { + 'Authorization': req.session && req.session.authorization + }); + rp({ + url: url, + method: 'DELETE', + headers: requestHeaders, + forever: constants.FOREVER_ON, + rejectUnauthorized: false, + }, function(error, response, body) { + if (utils.validateResponse('ProjectManagement.DELETE', error, response, body, resolve, reject)) { + return resolve({ + statusCode: response.statusCode, + data: JSON.stringify(response.body) + }); + }; + }); + }) +} + + +ProjectManagement.getPlatform = function(req, userId) { + var self = this; + var api_server = req.query['api_server']; + var user = req.params['userId'] || userId; + return new Promise(function(resolve, reject) { + var url = utils.confdPort(api_server) + '/' + API_VERSION + '/api/operational/rbac-platform-config'; + if(user) { + url = url + '/user/' + encodeURIComponent(user); + } + Promise.all([ + rp({ + uri: url, + method: 'GET', + headers: _.extend({}, constants.HTTP_HEADERS.accept.data, { + 'Authorization': req.session && req.session.authorization + }), + forever: constants.FOREVER_ON, + rejectUnauthorized: false, + resolveWithFullResponse: true + }) + ]).then(function(result) { + var response = {}; + response['data'] = {}; + if (result[0].body) { + if(user) { + response['data']['platform'] = JSON.parse(result[0].body)['rw-rbac-platform:user']; + } else { + response['data']['platform'] = JSON.parse(result[0].body)['rw-rbac-platform:rbac-platform-config']; + } + } + response.statusCode = constants.HTTP_RESPONSE_CODES.SUCCESS.OK + + resolve(response); + }).catch(function(error) { + var response = {}; + console.log('Problem with ProjectManagement.getPlatform', error); + response.statusCode = error.statusCode || 500; + response.errorMessage = { + error: 'Failed to get ProjectManagement.getPlatform' + error + }; + reject(response); + }); + }); +}; + +ProjectManagement.updatePlatform = function(req) { + var self = this; + var api_server = req.query['api_server']; + var bodyData = req.body; + data = bodyData; + data.user = JSON.parse(data.user) + var updateTasks = []; + + var updatePlatform = rp({ + uri: utils.confdPort(api_server) + '/' + API_VERSION + '/api/config/rbac-platform-config', + method: 'PUT', + headers: _.extend({}, constants.HTTP_HEADERS.accept.data, { + 'Authorization': req.session && req.session.authorization + }), + forever: constants.FOREVER_ON, + json: { + "rw-rbac-platform:rbac-platform-config": data + }, + rejectUnauthorized: false, + resolveWithFullResponse: true + }); + updateTasks.push(updatePlatform) + return new Promise(function(resolve, reject) { + Promise.all([ + updateTasks + ]).then(function(result) { + var response = {}; + response['data'] = {}; + if (result[0].body) { + response['data'] = result[0].body; + } + response.statusCode = constants.HTTP_RESPONSE_CODES.SUCCESS.OK + + resolve(response); + }).catch(function(error) { + var response = {}; + console.log('Problem with ProjectManagement.updatePlatform', error); + response.statusCode = error.statusCode || 500; + response.errorMessage = { + error: 'Failed to update platform - ' + error + }; + reject(response); + }); + }); +}; + + +module.exports = ProjectManagement; diff --git a/skyquake/framework/core/modules/api/restconf.js b/skyquake/framework/core/modules/api/restconf.js index 5ba0eb5aa..03f27213e 100644 --- a/skyquake/framework/core/modules/api/restconf.js +++ b/skyquake/framework/core/modules/api/restconf.js @@ -45,7 +45,7 @@ restconfAPI['streams'].get = function(req) { url: uri + url + '?deep', method: 'GET', headers: _.extend({}, constants.HTTP_HEADERS.accept.data, { - 'Authorization': req.get('Authorization') + 'Authorization': req.session && req.session.authorization }), forever: constants.FOREVER_ON, rejectUnauthorized: false, diff --git a/skyquake/framework/core/modules/api/schemaAPI.js b/skyquake/framework/core/modules/api/schemaAPI.js new file mode 100644 index 000000000..34c83be26 --- /dev/null +++ b/skyquake/framework/core/modules/api/schemaAPI.js @@ -0,0 +1,100 @@ +/* + * + * Copyright 2016 RIFT.IO Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +// DescriptorModelMeta API (NSD + VNFD) + + +var Schema = {}; +var rp = require('request-promise'); +var Promise = require('promise'); +var constants = require('../../api_utils/constants'); +var utils = require('../../api_utils/utils'); +var _ = require('lodash'); +var cors = require('cors'); +var bodyParser = require('body-parser'); +var utils = require('../../api_utils/utils'); +var configuration = require('./configuration'); + +var router = require('express').Router(); + + +router.use(bodyParser.json()); +router.use(cors()); +router.use(bodyParser.urlencoded({ + extended: true +})); + +router.get('/schema', cors(), function (req, res) { + getSchema(req).then(function (response) { + utils.sendSuccessResponse(response, res); + }, function (error) { + utils.sendErrorResponse(error, res); + }); +}); + +module.exports = { + getRouter: function () { + return router; + }, + init: function () {} +}; + +getSchema = function (req) { + var schemaURI = configuration.getBackendURL() + '/api/schema/'; + var schemaPaths = req.query['request']; + var paths = schemaPaths.split(','); + + function getSchemaRequest(path) { + return rp({ + uri: schemaURI + path, + method: 'GET', + headers: _.extend({}, constants.HTTP_HEADERS.accept.collection, { + 'Authorization': req.session && req.session.authorization + }), + forever: constants.FOREVER_ON, + rejectUnauthorized: false, + resolveWithFullResponse: true + }) + } + + var requests = _.map(paths, getSchemaRequest); + + return new Promise(function (resolve, reject) { + Promise.all(requests).then( + function (results) { + var data = { + schema: {} + } + _.forEach(results, function (result, index) { + data.schema[paths[index]] = JSON.parse(result.body); + }); + resolve({ + data: data, + statusCode: constants.HTTP_RESPONSE_CODES.SUCCESS.OK + }); + }).catch( + function (error) { + var response = {}; + console.log('Problem with schema.get', error); + response.statusCode = error.statusCode || 500; + response.errorMessage = { + error: 'Failed to get schema' + error + }; + reject(response); + }); + }); +}; \ No newline at end of file diff --git a/skyquake/framework/core/modules/api/sessions.js b/skyquake/framework/core/modules/api/sessions.js new file mode 100644 index 000000000..f16872454 --- /dev/null +++ b/skyquake/framework/core/modules/api/sessions.js @@ -0,0 +1,294 @@ +/* + * + * Copyright 2016 RIFT.IO Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +/** + * sessions api module. Provides API functions for sessions + * @module framework/core/modules/api/sessions + * @author Kiran Kashalkar + */ +"use strict" +var Promise = require('promise'); +var constants = require('../../api_utils/constants'); +var utils = require('../../api_utils/utils'); +var request = utils.request; +var rp = require('request-promise'); +var sessionsAPI = {}; +var _ = require('lodash'); +var base64 = require('base-64'); +var APIVersion = '/v2'; +var configurationAPI = require('./configuration'); +var UserManagement = require('./userManagementAPI.js'); +var URL = require('url'); + +// Used for determining what page a user should first go to. +var Application = { + order: [ + "rw-rbac-platform:super-admin", + "rw-rbac-platform:platform-admin", + "rw-rbac-platform:platform-oper", + "rw-project:project-admin", + "rw-project:project-oper", + "rw-project-mano:lcm-admin", + "rw-project-mano:lcm-oper", + "rw-project-mano:catalog-admin", + "rw-project-mano:catalog-oper", + "rw-project-mano:account-admin", + "rw-project-mano:account-oper" + ], + key: { + "rw-rbac-platform:super-admin": "user_management", + "rw-rbac-platform:platform-admin": "user_management", + "rw-rbac-platform:platform-oper": "user_management", + "rw-project:project-admin": "project_management", + "rw-project:project-oper": "project_management", + "rw-project-mano:catalog-admin": "composer", + "rw-project-mano:catalog-oper": "composer", + "rw-project-mano:lcm-admin": "launchpad", + "rw-project-mano:lcm-oper": "launchpad", + "rw-project-mano:account-admin": "accounts", + "rw-project-mano:account-oper": "accounts" + } +}; + +function logAndReject(mesg, reject, errCode) { + var res = {}; + res.errorMessage = { + error: mesg + } + res.statusCode = errCode || constants.HTTP_RESPONSE_CODES.ERROR.BAD_REQUEST; + console.log(mesg); + reject(res); +} + +function logAndRedirectToLogin(mesg, res, req, invalid) { + console.log(mesg); + if (!invalid) { + res.redirect(utils.buildRedirectURL(req, configurationAPI.globalConfiguration, 'login', '&referer=' + encodeURIComponent(req.headers.referer))); + } + res.end(); +} + +function logAndRedirectToEndSession(mesg, res, authorization, url) { + console.log(mesg); + res.set({ + 'Authorization': authorization + }); + res.redirect(url); + res.end(); +} +var sessionPromiseResolve = null; +sessionsAPI.sessionPromise = new Promise(function(resolve, reject) { + sessionPromiseResolve = resolve; +}); + +sessionsAPI.create = function (req, res) { + if (!req.session.passport){ + logAndRedirectToLogin("lost session", res, req); + return new Promise(function (resolve, reject){reject("lost session")}); + } + var api_server = req.query['api_server'] || (req.protocol + '://' + configurationAPI.globalConfiguration.get().api_server); + var uri = utils.confdPort(api_server); + var username = req.session.passport.user['username']; + var authorization_header_string = 'Bearer ' + req.session.passport.user.user.access_token; + return new Promise(function (resolve, reject) { + req.session.authorization = authorization_header_string; + req.session.api_server = api_server; + req.session.api_protocal = req.protocol; + req.session.loggedIn = true; + req.session.userdata = { + username: username, + }; + UserManagement.getUserInfo(req, req.session.passport.user.username).then(function (results) { + var project_list_for_user = null; + if (!req.session.projectId && results.data.project) { + project_list_for_user = Object.keys(results.data.project); + if (project_list_for_user.length > 0) { + req.session.projectId = project_list_for_user.sort() && project_list_for_user[0]; + } + } + sessionsAPI.setTopApplication(req); + req.session.isLCM = results.data.isLCM; + + req.session['ui-state'] = results.data['ui-state']; + var lastActiveProject = req.session['ui-state'] && req.session['ui-state']['last-active-project']; + if (lastActiveProject) { + if (results.data.project.hasOwnProperty(lastActiveProject)) { + req.session.projectId = lastActiveProject; + } + + } + + var successMsg = 'User => ' + username + ' successfully logged in.'; + successMsg += req.session.projectId ? 'Project => ' + req.session.projectId + ' set as default.' : ''; + + console.log(successMsg); + + req.session.save(function (err) { + if (err) { + console.log('Error saving session to store', err); + } + // no response data, just redirect now that session data is set + if (req.session['ui-state'] && req.session['ui-state']['last-active-uri']) { + var url = URL.parse(req.session['ui-state']['last-active-uri']); + var host = req.headers.host; + var path = url.path; + var hash = url.hash; + var protocol = url.protocol; + var newUrl = protocol + '//' + host + path + (hash?hash:''); + console.log('Redirecting to: ' + newUrl) + res.redirect(newUrl) + } else { + if(req.session.topApplication) { + res.redirect(utils.buildRedirectURL(req, configurationAPI.globalConfiguration, req.session.topApplication)); + } else { + res.redirect(utils.buildRedirectURL(req, configurationAPI.globalConfiguration, 'user_management', '#/user-profile')); + } + } + }) + + sessionPromiseResolve(req.session); + + }).catch(function (error) { + // Something went wrong - Redirect to /login + var errorMsg = 'Error logging in or getting list of projects. Error: ' + error; + console.log(errorMsg); + logAndRedirectToLogin(errorMsg, res, req); + }); + }) +}; + +sessionsAPI.addProjectToSession = function (req, res) { + return new Promise(function (resolve, reject) { + if (req.session && req.session.loggedIn == true) { + Promise.all([UserManagement.getProfile(req), UserManagement.updateActiveProject(req)]).then(function () { + req.session.projectId = req.params.projectId; + req.session.topApplication = null; + sessionsAPI.setTopApplication(req, req.query.app); + req.session.save(function (err) { + if (err) { + console.log('Error saving session to store', err); + var errorMsg = 'Session does not exist or not logged in'; + logAndReject(errorMsg, reject, constants.HTTP_RESPONSE_CODES.ERROR.NOT_FOUND); + } else { + var successMsg = 'Added project ' + req.session.projectId + ' to session ' + req.sessionID; + console.log(successMsg); + var response = { + statusCode: constants.HTTP_RESPONSE_CODES.SUCCESS.OK, + data: JSON.stringify({ + status: successMsg + }) + } + return resolve(response); + } + // res.redirect('/'); + }); + + }) + + } + }); +} + +sessionsAPI.delete = function (req, res) { + var idpServerAddress = configurationAPI.globalConfiguration.get().idp_server_address; + var idpServerProtocol = configurationAPI.globalConfiguration.get().idp_server_protocol; + var idpServerPortNumber = configurationAPI.globalConfiguration.get().idp_server_port_number; + var idpEndSessionPath = constants.END_SESSION_PATH; + var url = idpServerProtocol + '://' + + idpServerAddress + ':' + + idpServerPortNumber + '/' + + idpEndSessionPath; + var authorization = req.session.authorization; + return new Promise(function (resolve, reject) { + Promise.all([ + UserManagement.updateActiveUri(req), + new Promise(function (success, failure) { + req.session.destroy(function (err) { + if (err) { + var errorMsg = 'Error deleting session. Error: ' + err; + console.log(errorMsg); + success({ + status: 'error', + message: errorMsg + }); + } + + var successMsg = 'Success deleting session'; + console.log(successMsg); + + success({ + status: 'success', + message: successMsg + }); + }); + }) + ]).then(function (result) { + // assume the session was deleted! + var message = 'Session was deleted. Redirecting to end_session'; + resolve({ + statusCode: constants.HTTP_RESPONSE_CODES.SUCCESS.OK, + data: { + url: url, + message: message + } + }); + + }).catch(function (error) { + var message = "An error occured while deleting session"; + resolve({ + statusCode: constants.HTTP_RESPONSE_CODES.SUCCESS.OK, + data: { + url: url, + message: message + } + }); + }); + }); +} + +sessionsAPI.setTopApplication = function (req, suggestedPlugin) { + var selectedProject = req.session.projectId; + var userProject = selectedProject ? req.session.projectMap[selectedProject] : null; + if (userProject) { + if (suggestedPlugin) { + if (req.session.platformMap['rw-rbac-platform:super-admin']) { + topApplication = suggestedPlugin; + } else { + var roles = _.reduce(Object.keys(Application.key), function (accumulator, role) { + if (Application.key[role] === suggestedPlugin) { + accumulator.push(role); + } + return accumulator; + }, []); + if (_.some(roles, function (role){return userProject.role[role]})) { + req.session.topApplication = suggestedPlugin; + return; + } + } + } + _.some(Application.order, function (role) { + if (userProject.role[role] || req.session.platformMap.role[role]) { + req.session.topApplication = Application.key[role]; + return true; + } + return false; + }) + } +} + +module.exports = sessionsAPI; diff --git a/skyquake/framework/core/modules/api/userManagementAPI.js b/skyquake/framework/core/modules/api/userManagementAPI.js new file mode 100644 index 000000000..834df7ea9 --- /dev/null +++ b/skyquake/framework/core/modules/api/userManagementAPI.js @@ -0,0 +1,530 @@ +/* + * + * Copyright 2016 RIFT.IO Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +// DescriptorModelMeta API (NSD + VNFD) + + +var UserManagement = {}; +var Promise = require('bluebird'); +var rp = require('request-promise'); +var Promise = require('promise'); +var constants = require('../../api_utils/constants'); +var utils = require('../../api_utils/utils'); +var _ = require('lodash'); +var ProjectManagementAPI = require('./projectManagementAPI.js'); +var API_VERSION = 'v2'; + +UserManagement.get = function(req) { + var self = this; + var api_server = req.query['api_server']; + + return new Promise(function(resolve, reject) { + var userConfig = rp({ + uri: utils.confdPort(api_server) + '/' + API_VERSION + '/api/operational/user-config/user', + method: 'GET', + headers: _.extend({}, constants.HTTP_HEADERS.accept.data, { + 'Authorization': req.session && req.session.authorization + }), + forever: constants.FOREVER_ON, + rejectUnauthorized: false, + resolveWithFullResponse: true + }); + var userOp = rp({ + uri: utils.confdPort(api_server) + '/' + API_VERSION + '/api/operational/user-state/user', + method: 'GET', + headers: _.extend({}, constants.HTTP_HEADERS.accept.data, { + 'Authorization': req.session && req.session.authorization + }), + forever: constants.FOREVER_ON, + rejectUnauthorized: false, + resolveWithFullResponse: true + }) + Promise.all([ + userConfig, + userOp + ]).then(function(result) { + var response = {}; + var userConfig = []; + var userOpData = {}; + response['data'] = {}; + if (result[0].body) { + userConfig = JSON.parse(result[0].body)['rw-user:user']; + } + if (result[1].body) { + JSON.parse(result[1].body)['rw-user:user'].map(function(u) { + userOpData[u['user-domain'] + ',' + u['user-name']] = u; + }) + } + response.statusCode = constants.HTTP_RESPONSE_CODES.SUCCESS.OK + response['data']['user'] = userConfig.map(function(u,i) { + var mergedData = _.merge(u, userOpData[u['user-domain'] + ',' + u['user-name']]); + mergedData.projects = { + ids: [], + data: {} + }; + var projects = mergedData.projects; + mergedData.role && mergedData.role.map(function(r) { + if ((r.role != "rw-project:user-self" )&& (r.role != "rw-rbac-platform:user-self")) { + var projectId = r.keys.split(';')[0]; + if (projectId == "") { + projectId = "platform" + } + if (!projects.data[projectId]) { + projects.ids.push(projectId); + projects.data[projectId] = []; + } + projects.data[projectId].push(r.role); + } + }) + return mergedData; + }) + resolve(response); + }).catch(function(error) { + var response = {}; + console.log('Problem with UserManagement.get', error); + response.statusCode = error.statusCode || constants.HTTP_RESPONSE_CODES.ERROR.INTERNAL_SERVER_ERROR; + response.errorMessage = { + error: 'Failed to get UserManagement' + error + }; + reject(response); + }); + }); +}; + + +UserManagement.getProfile = function(req) { + var self = this; + var api_server = req.query['api_server']; + return new Promise(function(resolve, reject) { + var response = {}; + try { + var userId = req.session.userdata.username + response['data'] = { + userId: userId, + projectId: req.session.projectId, + domain: req.session.passport.user.domain + }; + UserManagement.getUserInfo(req, userId).then(function(result) { + response.statusCode = constants.HTTP_RESPONSE_CODES.SUCCESS.OK; + response.data.data = result.data + resolve(response); + }, function(error) { + console.log('Error retrieving getUserInfo'); + response.statusCode = constants.HTTP_RESPONSE_CODES.ERROR.INTERNAL_SERVER_ERROR; + reject(response); + }) + } catch (e) { + var response = {}; + console.log('Problem with UserManagement.get', error); + response.statusCode = error.statusCode || 500; + response.errorMessage = { + error: 'Failed to get UserManagement' + error + }; + reject(response); + } + }); +}; +UserManagement.getUserInfo = function(req, userId, domain) { + var self = this; + var api_server = req.query['api_server']; + var id = req.params['userId'] || userId; + var domain = req.params['domainId'] || domain; + var response = {}; + return new Promise(function(resolve, reject) { + if (id) { + var getProjects = ProjectManagementAPI.get(req, ['name', 'project-config']); + var getPlatformUser = ProjectManagementAPI.getPlatform(req, id); + var getUserUiState = UserManagement.getUserUiState(req); + Promise.all([ + getProjects, + getPlatformUser, + getUserUiState + ]).then(function(result) { + var userData = { + platform: { + role: { + + } + }, + //id/key values for each project + projectId:[], + project: { + /** + * [projectId] : { + * data: [project object], + * role: { + * [roleId]: true + * } + * } + */ + } + } + //Build UI state + var uiState = result[2].data && result[2].data['rw-user:user']; + userData['ui-state'] = uiState['ui-state']; + //Build platform roles + var platformRoles = result[1].data.platform && result[1].data.platform.role; + platformRoles && platformRoles.map(function(r) { + userData.platform.role[r.role] = true + }); + //Build project roles + var projects = result[0].data.project; + var userProjects = []; + projects && projects.map(function(p, i) { + userData.project[p.name] = { + data: p, + role: {} + } + userData.projectId.push(p.name); + if (userData.platform.role['rw-rbac-platform:super-admin']) { + userData.project[p.name] = { + data: p, + role: { + "rw-project:project-admin": true, + "rw-project:project-oper": true, + "rw-project-mano:account-admin": true, + "rw-project-mano:account-oper": true, + "rw-project-mano:catalog-admin": true, + "rw-project-mano:catalog-oper": true, + "rw-project-mano:lcm-admin": true, + "rw-project-mano:lcm-oper": true + } + } + } else { + var users = p['project-config'] && p['project-config'].user; + users && users.map(function(u) { + if(u['user-name'] == id) { + u.role && u.role.map(function(r) { + userData.project[p.name].role[r.role] = true; + if (r.role === 'rw-project:project-admin') { + userData.project[p.name].role["rw-project-mano:account-admin"] = true; + userData.project[p.name].role["rw-project-mano:catalog-admin"] = true; + userData.project[p.name].role["rw-project-mano:lcm-admin"] = true; + userData.isLCM = true; + } else if (r.role === 'rw-project:project-oper') { + userData.project[p.name].role["rw-project-mano:account-oper"] = true; + userData.project[p.name].role["rw-project-mano:catalog-oper"] = true; + userData.project[p.name].role["rw-project-mano:lcm-oper"] = true; + userData.isLCM = true; + } + }); + u["rw-project-mano:mano-role"] && u["rw-project-mano:mano-role"] .map(function(r) { + userData.project[p.name].role[r.role] = true; + if (r.role.indexOf('rw-project-mano:lcm') > -1) { + userData.isLCM = true; + } + }); + } + }) + } + }); + response.data = userData; + response.statusCode = constants.HTTP_RESPONSE_CODES.SUCCESS.OK; + + req.session.projectMap = userData.project; + req.session.platformMap = userData.platform; + resolve(response); + }) + } else { + var errorMsg = 'userId not specified in UserManagement.getUserInfo'; + console.error(errorMsg); + response.statusCode = constants.HTTP_RESPONSE_CODES.ERROR.BAD_REQUEST; + response.error = errorMsg; + reject(response) + } + + }) +} +UserManagement.create = function(req) { + var self = this; + var api_server = req.query['api_server']; + var data = req.body; + data = { + "user":[data] + } + return new Promise(function(resolve, reject) { + Promise.all([ + rp({ + uri: utils.confdPort(api_server) + '/' + API_VERSION + '/api/config/user-config', + method: 'POST', + headers: _.extend({}, constants.HTTP_HEADERS.accept.data, { + 'Authorization': req.session && req.session.authorization + }), + forever: constants.FOREVER_ON, + json: data, + rejectUnauthorized: false, + resolveWithFullResponse: true + }) + ]).then(function(result) { + var response = {}; + response['data'] = {}; + if (result[0].body) { + response['data'] = result[0].body; + } + response.statusCode = constants.HTTP_RESPONSE_CODES.SUCCESS.OK + + resolve(response); + }).catch(function(error) { + var response = {}; + console.log('Problem with UserManagement.create', error); + response.statusCode = error.statusCode || 500; + response.errorMessage = { + error: 'Failed to create user' + error + }; + reject(response); + }); + }); +}; +UserManagement.update = function(req) { + var self = this; + var api_server = req.query['api_server']; + var bodyData = req.body; + data = { + "rw-user:user": bodyData + } + var updateTasks = []; + if(bodyData.hasOwnProperty('old-password')) { + var changePW = rp({ + uri: utils.confdPort(api_server) + '/' + API_VERSION + '/api/operations/change-password', + method: 'POST', + headers: _.extend({}, constants.HTTP_HEADERS.accept.data, { + 'Authorization': req.session && req.session.authorization + }), + forever: constants.FOREVER_ON, + json: { + "input": { + 'user-name' : bodyData['user-name'], + 'user-domain' : bodyData['user-domain'], + 'old-password' : bodyData['old-password'], + 'new-password' : bodyData['new-password'], + 'confirm-password' : bodyData['confirm-password'], + } + }, + rejectUnauthorized: false, + resolveWithFullResponse: true + }); + updateTasks.push(changePW); + }; + var updateUser = rp({ + uri: utils.confdPort(api_server) + '/' + API_VERSION + '/api/config/user-config/user/' + encodeURIComponent(bodyData['user-name']) + ',' + encodeURIComponent(bodyData['user-domain']), + method: 'PUT', + headers: _.extend({}, constants.HTTP_HEADERS.accept.data, { + 'Authorization': req.session && req.session.authorization + }), + forever: constants.FOREVER_ON, + json: data, + rejectUnauthorized: false, + resolveWithFullResponse: true + }); + updateTasks.push(updateUser) + return new Promise(function(resolve, reject) { + Promise.all([ + updateTasks + ]).then(function(result) { + var response = {}; + response['data'] = {}; + if (result[0].body) { + response['data'] = result[0].body; + } + response.statusCode = constants.HTTP_RESPONSE_CODES.SUCCESS.OK + + resolve(response); + }).catch(function(error) { + var response = {}; + console.log('Problem with UserManagement.passwordChange', error); + response.statusCode = error.statusCode || 500; + response.errorMessage = { + error: 'Failed to passwordChange user' + error + }; + reject(response); + }); + }); +}; + +UserManagement.delete = function(req) { + var self = this; + var username = req.params.username; + var domain = req.params.domain; + var api_server = req.query["api_server"]; + var requestHeaders = {}; + var url = `${utils.confdPort(api_server)}/${API_VERSION}/api/config/user-config/user/${encodeURIComponent(username)},${encodeURIComponent(domain)}` + return new Promise(function(resolve, reject) { + _.extend(requestHeaders, + constants.HTTP_HEADERS.accept.data, + constants.HTTP_HEADERS.content_type.data, { + 'Authorization': req.session && req.session.authorization + }); + rp({ + url: url, + method: 'DELETE', + headers: requestHeaders, + forever: constants.FOREVER_ON, + rejectUnauthorized: false, + }, function(error, response, body) { + if (utils.validateResponse('UserManagement.DELETE', error, response, body, resolve, reject)) { + return resolve({ + statusCode: response.statusCode, + data: JSON.stringify(response.body) + }); + }; + }); + }) +}; +UserManagement.getUserUiState = function(req) { + var self = this; + var api_server = req.query['api_server']; + var user = req.session.passport.user; + return new Promise(function(resolve, reject) { + Promise.all([ + rp({ + uri: utils.confdPort(api_server) + '/' + API_VERSION + '/api/config/user-config/user/'+encodeURIComponent(user.username) + ',' + encodeURIComponent(user.domain), + method: 'GET', + headers: _.extend({}, constants.HTTP_HEADERS.accept.data, { + 'Authorization': req.session && req.session.authorization + }), + forever: constants.FOREVER_ON, + rejectUnauthorized: false, + resolveWithFullResponse: true + }) + ]).then(function(result) { + var response = {}; + response['data'] = {}; + if (result[0].body) { + response['data'] = JSON.parse(result[0].body); + } + response.statusCode = constants.HTTP_RESPONSE_CODES.SUCCESS.OK + + resolve(response); + }).catch(function(error) { + var response = {}; + console.log('Problem with UserManagement.getUserUiState', error); + response.statusCode = error.statusCode || 500; + response.errorMessage = { + error: 'Failed to create user' + error + }; + reject(response); + }); + }); +}; +UserManagement.updateActiveProject = function(req) { + var self = this; + var api_server = req.query['api_server']; + var user = req.session.passport.user; + var data = { + "rw-user:user-config": { + "user":{ + "user-name" : user.username, + "user-domain": user.domain, + "ui-state": { + "last-active-project" : req.params.projectId + } + } + } + } + return new Promise(function(resolve, reject) { + Promise.all([ + rp({ + uri: utils.confdPort(api_server) + '/' + API_VERSION + '/api/config/user-config', + method: 'PATCH', + headers: _.extend({}, constants.HTTP_HEADERS.accept.data, { + 'Authorization': req.session && req.session.authorization + }), + forever: constants.FOREVER_ON, + json: data, + rejectUnauthorized: false, + resolveWithFullResponse: true + }) + ]).then(function(result) { + var response = {}; + response['data'] = {}; + if (result[0].body) { + response['data'] = result[0].body; + } + response.statusCode = constants.HTTP_RESPONSE_CODES.SUCCESS.OK + + resolve(response); + }).catch(function(error) { + var response = {}; + console.log('Problem with UserManagement.updateActiveProject', error); + response.statusCode = error.statusCode || 500; + response.errorMessage = { + error: 'Failed to create user' + error + }; + reject(response); + }); + }); +}; +UserManagement.updateActiveUri = function(req) { + if (!req.session.passport) { + console.debug("passport gone before we got the save the active uri"); + var response = { + statusCode: 500, + errorMessage: { + error: 'Failed to save active uri' + }}; + return Promise.resolve(response); + } + var self = this; + var api_server = req.query['api_server']; + var user = req.session.passport.user; + var ref = req.headers.referer; + var hash = req.query.hash; + var data = { + "rw-user:user-config": { + "user":{ + "user-name" : user.username, + "user-domain": user.domain, + "ui-state": { + "last-active-uri" : ref + decodeURIComponent(hash) + } + } + } + } + return new Promise(function(resolve, reject) { + Promise.all([ + rp({ + uri: utils.confdPort(api_server) + '/' + API_VERSION + '/api/config/user-config', + method: 'PATCH', + headers: _.extend({}, constants.HTTP_HEADERS.accept.data, { + 'Authorization': req.session && req.session.authorization + }), + forever: constants.FOREVER_ON, + json: data, + rejectUnauthorized: false, + resolveWithFullResponse: true + }) + ]).then(function(result) { + var response = {}; + response['data'] = {}; + if (result[0].body) { + response['data'] = result[0].body; + } + response.statusCode = constants.HTTP_RESPONSE_CODES.SUCCESS.OK + + resolve(response); + }).catch(function(error) { + var response = {}; + console.log('Problem with UserManagement.updateActiveProject', error); + response.statusCode = error.statusCode || 500; + response.errorMessage = { + error: 'Failed to create user' + error + }; + reject(response); + }); + }); +}; +module.exports = UserManagement; diff --git a/skyquake/framework/core/modules/navigation_manager.js b/skyquake/framework/core/modules/navigation_manager.js index c85eba618..7d2239459 100644 --- a/skyquake/framework/core/modules/navigation_manager.js +++ b/skyquake/framework/core/modules/navigation_manager.js @@ -1,5 +1,5 @@ /* - * + * * Copyright 2016 RIFT.IO Inc * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -42,7 +42,6 @@ function addNavigation(plugin_name, routes) { if (!NAVIGATION[plugin_name]) { NAVIGATION[plugin_name] = {}; } - if (!NAVIGATION[plugin_name].routes) { NAVIGATION[plugin_name].routes = routes; } else { @@ -69,6 +68,20 @@ function addLabel(plugin_name, label) { NAVIGATION[plugin_name].label = label || 'RW.UI Plugin'; } +function addAllow(plugin_name, allow) { + if (!NAVIGATION[plugin_name]) { + NAVIGATION[plugin_name] = {}; + } + NAVIGATION[plugin_name].allow = allow || '*'; +} + +function addAdminFlag(plugin_name, admin_link) { + if (!NAVIGATION[plugin_name]) { + NAVIGATION[plugin_name] = {}; + } + NAVIGATION[plugin_name].admin_link = admin_link || false; +} + function getNavigation() { return NAVIGATION; } @@ -82,6 +95,8 @@ function onNavigationDiscovered(plugin_name, plugin) { addOrder(plugin_name, plugin.order); addPriority(plugin_name, plugin.priority); addLabel(plugin_name, plugin.name); + addAllow(plugin_name, plugin.allow); + addAdminFlag(plugin_name, plugin.admin_link); } function init() { diff --git a/skyquake/framework/core/modules/routes/auth.js b/skyquake/framework/core/modules/routes/auth.js new file mode 100644 index 000000000..c1df55a42 --- /dev/null +++ b/skyquake/framework/core/modules/routes/auth.js @@ -0,0 +1,99 @@ + +/* + * + * Copyright 2016 RIFT.IO Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +/** + * auth routes module. Provides a RESTful API for this + * skyquake instance's auth state. + * @module framework/core/modules/routes/auth + * @author Kiran Kashalkar + */ + +var cors = require('cors'); +var bodyParser = require('body-parser'); +var Router = require('express').Router(); +var utils = require('../../api_utils/utils'); +var configurationAPI = require('../api/configuration'); + +var auth = {}; + +auth.routes = function(authManager) { + console.log('Configuring auth routes'); + Router.use(bodyParser.json()); + Router.use(cors()); + Router.use(bodyParser.urlencoded({ + extended: true + })); + + // Define routes. + Router.get('/', function(req, res) { + var default_page = null; + var api_server = req.query['api_server'] || (req.protocol + '://' + configurationAPI.globalConfiguration.get().api_server); + if (req.session && req.session.topApplication) { + default_page = utils.buildRedirectURL(req, configurationAPI.globalConfiguration, req.session.topApplication); + } else { + default_page = utils.buildRedirectURL(req, configurationAPI.globalConfiguration, 'user_management', '#/user-profile'); + } + if (!req.user) { + res.redirect('/login'); + } else { + res.redirect(default_page); + } + }); + + Router.get('/login', cors(), function(req, res) { + // res.render('login.html'); + res.redirect('/login/idp'); + }); + + Router.get('/login/idp', + authManager.passport.authenticate('oauth2') + ); + + Router.get('/callback', function(req, res, next) { + authManager.passport.authenticate('oauth2', function(err, user, info) { + if (err) { + // Catch some errors specific to deployments (e.g. IDP unavailable) + if (err.oauthError && err.oauthError.code == 'ENOTFOUND') { + return res.render('idpconnectfail.ejs', { + callback_url: req.url + }); + } + return res.redirect('/login'); + } + if (!user) { + return res.redirect('/login'); + } + req.logIn(user, function(err) { + if (err) { + return next(err); + } + return res.redirect('/session?redirectParams=' + req.url); + }); + })(req, res, next); + }); + + + Router.get('/login.html', cors(), function(req, res) { + res.render('login.html'); + }); +} + +auth.router = Router; + +module.exports = auth; diff --git a/skyquake/framework/core/modules/routes/configuration.js b/skyquake/framework/core/modules/routes/configuration.js index b789ff043..93bb88aa1 100644 --- a/skyquake/framework/core/modules/routes/configuration.js +++ b/skyquake/framework/core/modules/routes/configuration.js @@ -56,38 +56,5 @@ Router.get('/server-configuration', cors(), function(req, res) { }); }); -Router.get('/check-auth', function(req, res) { - console.log('testing auth') - var api_server = req.query["api_server"]; - var uri = utils.confdPort(api_server) + '/api/config/'; - - checkAuth(uri, req).then(function(data) { - utils.sendSuccessResponse(data, res); - }, function(error) { - utils.sendErrorResponse(error, res); - }); -}); - -function checkAuth(uri, req){ - return new Promise(function(resolve, reject) { - request({ - uri: uri, - method: 'GET', - headers: _.extend({}, { - 'Authorization': req.get('Authorization'), - forever: CONSTANTS.FOREVER_ON, - rejectUnauthorized: false, - }) - }, function(error, response, body) { - console.log(arguments) - if( response.statusCode == 401) { - reject({statusCode: 401, error: response.body}); - } else { - resolve({statusCode:200, data:response.body}) - } - }); - }); -} - module.exports = Router; diff --git a/skyquake/framework/core/modules/routes/navigation.js b/skyquake/framework/core/modules/routes/navigation.js index 82c7ec580..37e86e41b 100644 --- a/skyquake/framework/core/modules/routes/navigation.js +++ b/skyquake/framework/core/modules/routes/navigation.js @@ -1,6 +1,6 @@ /* - * + * * Copyright 2016 RIFT.IO Inc * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -30,6 +30,7 @@ var navAPI = require('../api/navigation'); var Router = require('express').Router(); var utils = require('../../api_utils/utils'); var configurationAPI = require('../api/configuration'); +var csrfCheck = require('../../api_utils/csrf').csrfCheck; Router.use(bodyParser.json()); Router.use(cors()); @@ -37,48 +38,64 @@ Router.use(bodyParser.urlencoded({ extended: true })); -Router.get('/', cors(), function(req, res, next) { - res.redirect('/launchpad/?api_server=' + req.protocol + '://' + configurationAPI.globalConfiguration.get().api_server + '&upload_server=' + req.protocol + '://' + (configurationAPI.globalConfiguration.get().upload_server || req.hostname)); +//Should have a way of adding excluded routes to this via plugin registry, instead of hard coding +Router.use(/^(?!.*(login\/idp|session|composer\/upload|composer\/update)).*/, function(req, res, next) { + var api_server = req.query['api_server'] || (req.protocol + '://' + configurationAPI.globalConfiguration.get().api_server); + if (req.session && req.session.loggedIn) { + switch (req.method) { + case 'POST': + case 'PUT': + csrfCheck(req, res, next); + break; + default: + next(); + break; + } + } else { + console.log('Redirect to login.html'); + res.redirect(utils.buildRedirectURL(req, configurationAPI.globalConfiguration, 'login', '&referer=' + encodeURIComponent(req.headers.referer))); + } }); + Router.get('/nav', cors(), function(req, res) { - navAPI.get(req).then(function(data) { - utils.sendSuccessResponse(data, res); - }, function(error) { - utils.sendErrorResponse(error, res); - }); + navAPI.get(req).then(function(data) { + utils.sendSuccessResponse(data, res); + }, function(error) { + utils.sendErrorResponse(error, res); + }); }); Router.get('/nav/:plugin_id', cors(), function(req, res) { - navAPI.get(req).then(function(data) { - utils.sendSuccessResponse(data, res); - }, function(error) { - utils.sendErrorResponse(error, res); - }); + navAPI.get(req).then(function(data) { + utils.sendSuccessResponse(data, res); + }, function(error) { + utils.sendErrorResponse(error, res); + }); }); Router.post('/nav/:plugin_id', cors(), function(req, res) { - navAPI.create(req).then(function(data) { - utils.sendSuccessResponse(data, res); - }, function(error) { - utils.sendErrorResponse(error, res); - }); + navAPI.create(req).then(function(data) { + utils.sendSuccessResponse(data, res); + }, function(error) { + utils.sendErrorResponse(error, res); + }); }); Router.put('/nav/:plugin_id/:route_id', cors(), function(req, res) { - navAPI.update(req).then(function(data) { - utils.sendSuccessResponse(data, res); - }, function(error) { - utils.sendErrorResponse(error, res); - }); + navAPI.update(req).then(function(data) { + utils.sendSuccessResponse(data, res); + }, function(error) { + utils.sendErrorResponse(error, res); + }); }); Router.delete('/nav/:plugin_id/:route_id', cors(), function(req, res) { - navAPI.delete(req).then(function(data) { - utils.sendSuccessResponse(data, res); - }, function(error) { - utils.sendErrorResponse(error, res); - }); + navAPI.delete(req).then(function(data) { + utils.sendSuccessResponse(data, res); + }, function(error) { + utils.sendErrorResponse(error, res); + }); }); diff --git a/skyquake/framework/core/modules/routes/projectManagement.js b/skyquake/framework/core/modules/routes/projectManagement.js new file mode 100644 index 000000000..c106f30e3 --- /dev/null +++ b/skyquake/framework/core/modules/routes/projectManagement.js @@ -0,0 +1,85 @@ +/* + * + * Copyright 2016 RIFT.IO Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +/** + * inactivity routes module. Provides a RESTful API for this + * skyquake instance's inactivity state. + * @module framework/core/modules/routes/inactivity + * @author Laurence Maultsby + */ + +var cors = require('cors'); +var bodyParser = require('body-parser'); +var Router = require('express').Router(); +var utils = require('../../api_utils/utils'); +var ProjectManagementAPI = require('../api/projectManagementAPI.js'); + +Router.use(bodyParser.json()); +Router.use(cors()); +Router.use(bodyParser.urlencoded({ + extended: true +})); + +Router.get('/project', cors(), function(req, res) { + ProjectManagementAPI.get(req).then(function(response) { + utils.sendSuccessResponse(response, res); + }, function(error) { + utils.sendErrorResponse(error, res); + }); +}); +Router.post('/project', cors(), function(req, res) { + ProjectManagementAPI.create(req).then(function(response) { + utils.sendSuccessResponse(response, res); + }, function(error) { + utils.sendErrorResponse(error, res); + }); +}); +Router.put('/project', cors(), function(req, res) { + ProjectManagementAPI.update(req).then(function(response) { + utils.sendSuccessResponse(response, res); + }, function(error) { + utils.sendErrorResponse(error, res); + }); +}); +Router.delete('/project/:projectname', cors(), function(req, res) { + ProjectManagementAPI.delete(req).then(function(response) { + utils.sendSuccessResponse(response, res); + }, function(error) { + utils.sendErrorResponse(error, res); + }); +}); + +Router.put('/platform', cors(), function(req, res) { + ProjectManagementAPI.updatePlatform(req).then(function(response) { + utils.sendSuccessResponse(response, res); + }, function(error) { + utils.sendErrorResponse(error, res); + }); +}); + +Router.get('/platform', cors(), function(req, res) { + ProjectManagementAPI.getPlatform(req).then(function(response) { + utils.sendSuccessResponse(response, res); + }, function(error) { + utils.sendErrorResponse(error, res); + }); +}); +module.exports = Router; + + + diff --git a/skyquake/framework/core/modules/routes/sessions.js b/skyquake/framework/core/modules/routes/sessions.js new file mode 100644 index 000000000..f84ca11b7 --- /dev/null +++ b/skyquake/framework/core/modules/routes/sessions.js @@ -0,0 +1,74 @@ + +/* + * + * Copyright 2016 RIFT.IO Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +/** + * Node sessions routes module. + * Provides a RESTful API to manage sessions. + * @module framework/core/modules/routes/sessions + * @author Kiran Kashalkar + */ + +var cors = require('cors'); +var bodyParser = require('body-parser'); +var sessionsAPI = require('../api/sessions'); +var Router = require('express').Router(); +var utils = require('../../api_utils/utils'); +var CONSTANTS = require('../../api_utils/constants.js'); +var request = require('request'); +var _ = require('lodash'); + +var sessions = {}; + +sessions.routes = function(sessionsConfig) { + Router.use(bodyParser.json()); + Router.use(cors()); + Router.use(bodyParser.urlencoded({ + extended: true + })); + + // Overloaded get method to handle OpenIDConnect redirect to establish a session. + Router.get('/session*', cors(), /*sessionsConfig.authManager.passport.authenticate('main', { + noredirect: false + }), */function(req, res) { + req.query['api_server'] = sessionsConfig.api_server_protocol + '://' + sessionsConfig.api_server; + sessionsAPI.create(req, res).then(function(data) { + utils.sendSuccessResponse(data, res); + }); + }); + + // For project switcher UI + Router.put('/session/:projectId', cors(), function(req, res) { + sessionsAPI.addProjectToSession(req, res).then(function(data) { + utils.sendSuccessResponse(data, res); + }, function(error) { + utils.sendErrorResponse(error, res); + }); + }); + + Router.delete('/session', cors(), function(req, res) { + sessionsAPI.delete(req, res).then(function(data) { + utils.sendSuccessResponse(data, res); + }); + }); +} + +sessions.router = Router; + + +module.exports = sessions; diff --git a/skyquake/framework/core/modules/routes/userManagement.js b/skyquake/framework/core/modules/routes/userManagement.js new file mode 100644 index 000000000..22a2d74e0 --- /dev/null +++ b/skyquake/framework/core/modules/routes/userManagement.js @@ -0,0 +1,85 @@ + +/* + * + * Copyright 2016 RIFT.IO Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +/** + * inactivity routes module. Provides a RESTful API for this + * skyquake instance's inactivity state. + * @module framework/core/modules/routes/inactivity + * @author Laurence Maultsby + */ + +var cors = require('cors'); +var bodyParser = require('body-parser'); +var Router = require('express').Router(); +var utils = require('../../api_utils/utils'); +var UserManagementAPI = require('../api/userManagementAPI.js'); + +Router.use(bodyParser.json()); +Router.use(cors()); +Router.use(bodyParser.urlencoded({ + extended: true +})); + +Router.get('/user', cors(), function(req, res) { + UserManagementAPI.get(req).then(function(response) { + utils.sendSuccessResponse(response, res); + }, function(error) { + utils.sendErrorResponse(error, res); + }); +}); +Router.get('/user-profile', cors(), function(req, res) { + UserManagementAPI.getProfile(req).then(function(response) { + utils.sendSuccessResponse(response, res); + }, function(error) { + utils.sendErrorResponse(error, res); + }); +}); +Router.get('/user-data/:userId/:domain?', cors(), function(req, res) { + UserManagementAPI.getUserInfo(req).then(function(response) { + utils.sendSuccessResponse(response, res); + }, function(error) { + utils.sendErrorResponse(error, res); + }); +}); +Router.post('/user', cors(), function(req, res) { + UserManagementAPI.create(req).then(function(response) { + utils.sendSuccessResponse(response, res); + }, function(error) { + utils.sendErrorResponse(error, res); + }); +}); +Router.put('/user', cors(), function(req, res) { + UserManagementAPI.update(req).then(function(response) { + utils.sendSuccessResponse(response, res); + }, function(error) { + utils.sendErrorResponse(error, res); + }); +}); +Router.delete('/user/:username/:domain', cors(), function(req, res) { + UserManagementAPI.delete(req).then(function(response) { + utils.sendSuccessResponse(response, res); + }, function(error) { + utils.sendErrorResponse(error, res); + }); +}); + +module.exports = Router; + + + diff --git a/skyquake/framework/core/views/home.ejs b/skyquake/framework/core/views/home.ejs new file mode 100644 index 000000000..2cb2f80cc --- /dev/null +++ b/skyquake/framework/core/views/home.ejs @@ -0,0 +1,5 @@ +<% if (!user) { %> +

Welcome! Please log in.

+<% } else { %> +

Hello, <%= user.username %>. View your default page. TODO: Update link to dashboard

+<% } %> \ No newline at end of file diff --git a/skyquake/framework/core/views/idpconnectfail.ejs b/skyquake/framework/core/views/idpconnectfail.ejs new file mode 100644 index 000000000..7847b3db9 --- /dev/null +++ b/skyquake/framework/core/views/idpconnectfail.ejs @@ -0,0 +1,43 @@ + +
+

+ We are having trouble connecting to the Identity Provider. +

+

+ Please check that it is running and reachable. +

+ +
\ No newline at end of file diff --git a/skyquake/framework/plugin-index.html b/skyquake/framework/plugin-index.html new file mode 100644 index 000000000..b704a3c10 --- /dev/null +++ b/skyquake/framework/plugin-index.html @@ -0,0 +1,13 @@ + + +
\ No newline at end of file diff --git a/skyquake/framework/source/SourceCache.js b/skyquake/framework/source/SourceCache.js new file mode 100644 index 000000000..ded3c9d32 --- /dev/null +++ b/skyquake/framework/source/SourceCache.js @@ -0,0 +1,96 @@ +/* + * + * Copyright 2017 RIFT.IO Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +import appConfiguration from '../utils/appConfiguration' + +let versionKeyPrefix = null; + +const localCache = new class { + get(key) { + let valueAsString = localStorage.getItem(key); + return valueAsString ? JSON.parse(valueAsString) : undefined; + } + set(key, val) { + localStorage.setItem(key, typeof val === 'string' ? val : JSON.stringify(val)); + } +}(); + +let objCache = new Map(); + +const storeCache = new class { + get(key) { + if (objCache[key]) { + objCache[key].timerId && clearTimeout(objCache[key].timerId) + objCache[key].timerId = setTimeout((key) => delete objCache[key], 2000) + return objCache[key].value; + } + const obj = localCache.get(key); + if (obj) { + objCache[key] = { + value: obj, + timerId: setTimeout((key) => delete objCache[key], 2000) + } + return obj; + } + } + set(key, obj) { + setTimeout(localCache.set, 100, key, obj); + objCache[key] = { + value: obj, + timerId: setTimeout((key) => delete objCache[key], 2000) + } + } + init(version) { + versionKeyPrefix = 's-v-' + version; + const currentStoreVersion = localStorage.getItem('store-version'); + if (currentStoreVersion !== version) { + let removeItems = []; + for (let i = 0; i < localStorage.length; ++i) { + let key = localStorage.key(i); + if (key.startsWith('s-v-')) { + removeItems.push(key); + } + } + removeItems.forEach((key) => localStorage.removeItem(key)); + localStorage.setItem('store-version', version); + } + } +}(); + +class StoreCache { + constructor(name) { + this.name = 's-v-' + name; + } + get(key) { + return storeCache.get(this.name + key); + } + set(key, obj) { + storeCache.set(this.name + key, obj); + } + init() { + return versionKeyPrefix ? Promise.resolve() : new Promise( + (resolve, reject) => { + appConfiguration.get().then((config) => { + storeCache.init(config.version); + resolve(); + }) + } + ) + } +} + +module.exports = StoreCache; \ No newline at end of file diff --git a/skyquake/framework/source/model/index.js b/skyquake/framework/source/model/index.js new file mode 100644 index 000000000..019ada0a9 --- /dev/null +++ b/skyquake/framework/source/model/index.js @@ -0,0 +1,7 @@ +import modelActions from './modelActions' +import modelSource from './modelSource' + +export { + modelSource, + modelActions +} \ No newline at end of file diff --git a/skyquake/framework/source/model/modelActions.js b/skyquake/framework/source/model/modelActions.js new file mode 100644 index 000000000..41dbc00e4 --- /dev/null +++ b/skyquake/framework/source/model/modelActions.js @@ -0,0 +1,31 @@ +/* + * + * Copyright 2017 RIFT.IO Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +import Alt from 'widgets/skyquake_container/skyquakeAltInstance'; + +class Actions { + + constructor() { + this.generateActions( + 'loadModel', + 'processRequestSuccess', + 'processRequestInitiated', + 'processRequestFailure'); + } +} + +export default Alt.createActions(Actions); diff --git a/skyquake/framework/source/model/modelSource.js b/skyquake/framework/source/model/modelSource.js new file mode 100644 index 000000000..3779aa5ca --- /dev/null +++ b/skyquake/framework/source/model/modelSource.js @@ -0,0 +1,128 @@ +/* + * + * Copyright 2017 RIFT.IO Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +import rw from 'utils/rw' +import modelActions from './modelActions' +import Utils from 'utils/utils' +import $ from 'jquery'; + +const source = { + loadModel: function () { + return { + remote: function (state, path) { + return new Promise(function (resolve, reject) { + $.ajax({ + url: '/model?path=' + path, + type: 'GET', + success: function (result) { + resolve(result); + }, + error: function (xhr, errorType, errorMessage) { + console.log("There was an error updating the model: ", errorType, errorMessage, xhr); + reject({response: xhr.responseJSON, errorType, errorMessage}); + } + }); + }) + }, + success: modelActions.processRequestSuccess, + loading: modelActions.processRequestInitiated, + error: modelActions.processRequestFailure + } + }, + updateModel: function () { + return { + remote: function (state, path, data) { + const url = path.reduce((url, node) => { + url += node[0] !== '[' ? '/' : ''; + return url + node + }, `/model?path=/${state.path}`); + return new Promise(function (resolve, reject) { + $.ajax({ + url: url, + type: 'PATCH', + data: data, + success: function (result) { + resolve(result); + }, + error: function (xhr, errorType, errorMessage) { + console.log("There was an error updating the model: ", errorType, errorMessage, xhr); + reject({response: xhr.responseJSON, errorType, errorMessage}); + } + }); + }) + }, + success: modelActions.processRequestSuccess, + loading: modelActions.processRequestInitiated, + error: modelActions.processRequestFailure + } + }, + createModel: function () { + return { + remote: function (state, path, data) { + const url = path.reduce((url, node) => { + url += node[0] !== '[' ? '/' : ''; + return url + node + }, `/model?path=/${state.path}`); + return new Promise(function (resolve, reject) { + $.ajax({ + url: url, + type: 'PUT', + data: data, + success: function (result) { + resolve(result); + }, + error: function (xhr, errorType, errorMessage) { + console.log("There was an error updating the model: ", errorType, errorMessage, xhr); + reject({response: xhr.responseJSON, errorType, errorMessage}); + } + }); + }) + }, + success: modelActions.processRequestSuccess, + loading: modelActions.processRequestInitiated, + error: modelActions.processRequestFailure + } + }, + + deleteModel: function () { + return { + remote: function (state, path) { + const url = path.reduce((url, node) => { + url += node[0] !== '[' ? '/' : ''; + return url + node + }, `/model?path=/${state.path}`); + return new Promise(function (resolve, reject) { + $.ajax({ + url: url, + type: 'DELETE', + success: function (result) { + resolve(result); + }, + error: function (xhr, errorType, errorMessage) { + console.log("There was an error updating the model: ", errorType, errorMessage, xhr); + reject({response: xhr.responseJSON, errorType, errorMessage}); + } + }); + }) + }, + success: modelActions.processRequestSuccess, + loading: modelActions.processRequestInitiated, + error: modelActions.processRequestFailure + } + } +} +module.exports = source; \ No newline at end of file diff --git a/skyquake/framework/source/schema/index.js b/skyquake/framework/source/schema/index.js new file mode 100644 index 000000000..95b152e29 --- /dev/null +++ b/skyquake/framework/source/schema/index.js @@ -0,0 +1,7 @@ +import schemaActions from './schemaActions' +import schemaSource from './schemaSource' + +export { + schemaSource, + schemaActions +} \ No newline at end of file diff --git a/skyquake/framework/source/schema/schemaActions.js b/skyquake/framework/source/schema/schemaActions.js new file mode 100644 index 000000000..7e36cca20 --- /dev/null +++ b/skyquake/framework/source/schema/schemaActions.js @@ -0,0 +1,31 @@ +/* + * + * Copyright 2017 RIFT.IO Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +import Alt from 'widgets/skyquake_container/skyquakeAltInstance'; + +class Actions { + + constructor() { + this.generateActions( + 'loadSchema', + 'loadSchemaSuccess', + 'loadSchemaLoading', + 'loadSchemaFail'); + } +} + +export default Alt.createActions(Actions); diff --git a/skyquake/framework/source/schema/schemaSource.js b/skyquake/framework/source/schema/schemaSource.js new file mode 100644 index 000000000..2c7bd5bcb --- /dev/null +++ b/skyquake/framework/source/schema/schemaSource.js @@ -0,0 +1,95 @@ +/* + * + * Copyright 2017 RIFT.IO Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +import rw from 'utils/rw' +import schemaActions from './schemaActions' +import Utils from 'utils/utils' +import $ from 'jquery'; +import StoreCache from '../SourceCache' + +const storeCache = new StoreCache('schema'); +storeCache.init(); // get the ball rolling + +function getCachedSchema(request) { + const cachedSchema = {}; + const requestSchema = []; + request.forEach((path) => { + let schema = storeCache.get(path); + if (schema) { + cachedSchema[path] = schema + } else { + requestSchema.push(path); + } + }); + return { + cachedSchema, + requestSchema + }; +} + +const schemaSource = { + loadSchema: function () { + return { + local: function (state, request) { + request = Array.isArray(request) ? request : [request]; + const results = getCachedSchema(request); + if (!results.requestSchema.length) { + return(Promise.resolve(results.cachedSchema)); + } + }, + + remote: function (state, request) { + return new Promise(function (resolve, reject) { + storeCache.init().then(() => { + request = Array.isArray(request) ? request : [request]; + const results = getCachedSchema(request); + if (!results.requestSchema.length) { + resolve({ + schema: results.cachedSchema + }); + } else { + $.ajax({ + url: '/schema?request=' + results.requestSchema.join(','), + type: 'GET', + success: function ({ + schema + }) { + for (let path in schema) { + storeCache.set(path, schema[path]); + } + resolve(getCachedSchema(request).cachedSchema); + }, + error: function (error) { + console.log("There was an error getting the schema: ", error); + reject(error); + } + }).fail(function (xhr) { + console.log("There was an error getting the schema: ", xhr); + Utils.checkAuthentication(xhr.status); + reject("There was an error getting the schema.") + }); + } + }) + }) + }, + success: schemaActions.loadSchemaSuccess, + loading: schemaActions.loadSchemaLoading, + error: schemaActions.loadSchemaFail + } + }, +} +export default schemaSource; \ No newline at end of file diff --git a/skyquake/framework/style/_colors.scss b/skyquake/framework/style/_colors.scss index 9378984ba..a01c293f3 100644 --- a/skyquake/framework/style/_colors.scss +++ b/skyquake/framework/style/_colors.scss @@ -1,6 +1,6 @@ /* - * + * * Copyright 2016 RIFT.IO Inc * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -31,7 +31,7 @@ $body-color:$lightest-gray; $error-red:#FF5F5F; -//PC +/*PC*/ $black: #000; $gray-lightest: #f1f1f1; @@ -42,9 +42,9 @@ $gray-dark: #999; $gray-darker: #666; $gray-darkest: #333; $white: #FFF; -// -// Brand Colors -// +/**/ +/* Brand Colors*/ +/**/ $brand-blue-light: #30baef; $brand-blue: #00acee; $brand-blue-dark: #147ca3; @@ -66,11 +66,12 @@ $neutral-light-3: hsl(360, 100%, 50%); $neutral-light-4: hsl(360, 100%, 50%); $neutral-light-5: hsl(360, 100%, 50%); -$neutral-dark-1: hsl(360, 100%, 50%); -$neutral-dark-2: hsl(360, 100%, 50%); -$neutral-dark-3: hsl(360, 100%, 50%); -$neutral-dark-4: hsl(360, 100%, 50%); -$neutral-dark-5: hsl(360, 100%, 50%); +$neutral-dark-0: hsl(0, 0%, 80.7%); +$neutral-dark-1: hsl(0, 0%, 63.7%); +$neutral-dark-2: hsl(0, 0%, 56.7%); +$neutral-dark-3: hsl(0, 0%, 49.7%); +$neutral-dark-4: hsl(0, 0%, 42.7%); +$neutral-dark-5: hsl(0, 0%, 35.7%); $netral-black: hsl(0, 100%, 0%); diff --git a/skyquake/framework/style/core.css b/skyquake/framework/style/core.css index 01b728b47..e9d5de9cf 100644 --- a/skyquake/framework/style/core.css +++ b/skyquake/framework/style/core.css @@ -16,8 +16,6 @@ * */ /*@import "./vendor/css-reset-2.0/css-reset.css";*/ -@import "../../node_modules/reset-css/reset.css"; - .has-drop-shadow { box-shadow: 2px 2px rgba(0, 0, 0, .15) } @@ -2023,7 +2021,7 @@ a[role=button] { margin-top: 150px; margin-bottom: 20px; background-size: 154px 102px; - /*background-image: url(./img/header-logo.png)*/ + /*background-image: url(./img/header-logo.png)*/ background-image: url(./img/svg/osm-logo_color_rgb.svg); } .login-cntnr .riftio { diff --git a/skyquake/framework/style/img/osm_header.png b/skyquake/framework/style/img/osm_header.png index 53a9500636b86f56ac99c8f4fdc1b758254fcaea..c67dca3fa70b00d1a533f08d048048e7f17ab9d7 100644 GIT binary patch literal 8767 zcmbVS<69jL1I@K;`)1s1+iTglnag$4vRB<|X>HlYs+(=w3(L0g{{Dvd!@=kC={)B- zF<(9_V4#ws!oa{_C@IQn{-?A5fd>WYKi&z^wf;{KY^BtsU|{MJ(OyA_FfcUtO0rVg zVAwMgFd0+7CKNcX*^LE z2CV@i_FwWh0T@4{gXoYf3KwN*6A;=IzhiRCSBy|TOIbJ!4pfN51yP;q$wBgwCRHs^ z$->r7rH!jBF=!ER{?UcQ{>(a1oF>(>n*#!=+733O$a^voq(dwNk2lIvgvhY!?X54d zi=74^sqmJ5!LNUOQ&OTy$T9erq@^iXR5%Yct3uV>?Op;1`E(#v-E#h8Ifv~Hi?i77 zDU+tIuA%%v7@5SVYUzG|oy1X~0f#)+C4`tjHz1zCJQ-@Lt*oB96qDOJ)h;!@o&Sr# zqs(pzDd^@GgX5IMc30`yzxKwC0wpd9?0!u5^qU9;k(%+WhE4?p+W->qs5Ze~NK{)@ z+2V`RVcQ|8)N?>Bn;tLU({Ln>&;(K?3hOMYDO|<-ASY_nZ{r7jUF)0lbyHS)r|DZ_ zlo8G1)o-k-6!HpD(qBriG6GL_ zBl_EWW3M}ivKhcfgtyrL?$rpo6Y#SK24(Ivt`%#{yVX-iBy^rnPKP9GNC!DwvGwZ? z(W@_i#oY=$LB`15jFQW%nxH-U!6}FIA?BcS0RVZzQ~KI6vu8D#B_Abd__vSvbN0$w z%pafnv;+em`_-O?4`j-G1>yd!`%ZJvh-T>z_ZzVLp?M>EO?ou9UOl+#A≷Y)?)Xju)&lKiNE3jDM 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 literal 131139 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

lxQdX_JvI8Aa4?#H>ParBO|BtTfIGs}{L} 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ɗ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 Bi^c!| literal 9663 zcmY*fbyQSuv>lXGI;6Wpex!8Q&>hmFNJvPBq;yMzbV)Y?f;7_cKHhr&yv3Ti z!`!(u-@137efHkxMk*;tp}Zk@1A#zLq@m&}U>gY@Y>4pS*^>UoA8cSPMC3&vklOgS z4@Pj{e=-xOiaZ43MFW8Zgg_v7;H`jt2*i~I0y!{*KzP$35L}0>R%L#$1KwCxN*wb1 z^7pl)BpJMeGQKS%L zs=$y%GIqp>UmBAP|I`g|V#qpGLcP|tby6F(`Luj1?JCz^NiaqIjMf(y7QtY&Ok|k| zLnnj59CBdG$-4d9aZDOcgo&6C@b7AEnKbvo@k-k}w(GINLSUlqou~|bizwbEi|g1} z3KxfCMZZ{s5H#A}5L#o+fI}$Qb8vBbniW1QBlm*@%ehGX>FqjMJ)>t!ZBZz3>fA%e z)R^XU$Ou93dS=ijOK^S=`cOb_@eWgpLqu;0|Bl)$+pj7|c`pWOH$v3#*AnVoQwrg| zMujQqa>bwO*Zi*s;-XX|FBIeEhqOd053p< zdNImAp`@ob)^WWS-EV9(SFS$$>(}}H`ZGz%%x!E#5#v6a^s7L~2?^bA*De|72__Et z!z?0*D7w7^Mn&boT9Z&IL)b)k*B0VcP#~E&)tCIB1lU7e4m*Z^1FZ1;k5CNxl>`%q zlN%!V9kT$Xs^)^j`D%-^t$rr`cD!&(ib~NubmzTsvxl2Axm>~Q#yoV<4YxLBb)2?U zKmCt_x$%Z95i@_pv;IVhmPCDJs$%L;%qVmP>$&309{~0$e7kIAPbBRV={0trn$3_jG zptMSJ!K|Jb>{&H9tD;8A$Ov;YCo@}yAO%E-V~85ww8JWB8jXx0sKe1KS7<1yyC^bR zS6~&26YxTccc_wRaHlU91$}<|>F&a=@zN}4ImXq-_<(rDL}$diGK#82|{gcnOt36+n??ZM{@+eEG%e2*1S&yjE#-c`90ab zaD6hLuhLV~)ZBJz@c`co@%Wvp$?bE)0x6J+H~&@fal!TjxKbmZ-<$M?q3pLR=-TVx zPFZ}MvUgw5=7LtJ>sZ?P`r{hmz?Kbj~#f75IZ;{Lf_ZwVsb)L>G!uY%# z{f{}d#Nb7Ps>&+L1b#$eE#u2GxMCfa;!omI^1!L9Phln_5;Kb&7>V;KN1~&nVwHdE zPRfZ`GZsA{$CeP&-qKPx%e$)V;P>7Wc4GI|e<$stCv{|sTU2hXrbZYhA|e7aQ!L+W zUYD4bHh6WkyjX9A^>X>gGC5bACj?^fSYHK%lXyjtK5QUxeE9HcvOo%r#OG8bDLMJi zObOaa`vvM?9C0)=y&bvQa5X0PyTKznIk2`+qytT1=4mUAL`+ zr6C9_D`SxQKd?5f`|;V#SD68LV!wO$>J(HA`=>R2ZEfv{=;$_X>Bclt3+aWR&HCh) zD^(i%m9e4i*g^!jdoMYv-mStk#omok?W*2u6NJ+fI3B7KyF9CB-m5~pt?jedi>aoP zf2OeJj3qwViA2jr>~n}3dI;Rar%|eA?-jp?}S+u(diKP98x!-{MG( z$tg>dNU8c^-{elF3QEMl@0L1GDFRTE)8)SMn*Xye+4nFm4vxv`=~u$$<}?6vQ_^+I zM>mg83WviJ6BYoFfFSDJj$OPb*j-EcRr3F6#)QK)LE=l~}7L}E~-S7Ws z^&*N{+{X=u-DUI&322nYpz7!)A012IxlkR=X@1L3w0AQmlZK(0524q(g1i1)`PJ{Q z<+T5qLC6;Z)_z)wKqWiQ?j>ucjK%%RN<<06KXY%rp9%O8w3V0~1H|V>vAIglmQxWt zsw}B$#TVy;+_3wm)qt4KbvWlXweJ8;X9p?|HfPYDYPvs@#zZw|b<@vyeG z=JkCxu35B|%jAfZPNsRGuN;ppBzP2p5YPhD=TJ@U?up-ow$6QufKFW{!Bdb1uiBT? z)?#$6D}pjCQOJ(EGCJQGR(>p1O@h+j?i=;d(z?WXxIc#Ai4FZ2!2h z0TO~oBU$;A?urArsi3j(^OsMZJa@JWHAXQc0-1KpjgB+&To|a~fa|9&9k^qv4%5eW z6%P5DR*&+m2tfZj^a?+3*Z)rx5fcl`^KwcGQ0|}0M+KaDl-$SICjWB|XCdAprRq23 zOZ52I%quJ~vmx3%$Di_>I0Mhlcyw)KOvY)cExUX&DZ3t6du#`OHIeohQ?IOqu;@^< zkX@N<8JRa>MoOR* zBW5k75T!$gW%#b%>?f40tc#m0NCgUHXwAhiqx3*ALo>d7 z8AC3=Hj$ANG+|E+IXGmYrfyuSH5}T8GV(n)GgefU0HJ~a#*e&(B&g+Gv2c7ty zc)uvQ3WKg3N~yRSr{6N~m~|tJ@Sya3K4s__7uoVF3$-TFQ0PX6MUK(lcs3EgM*`g2 zcMdy47*%b@yv8ObEt_G49%ucyi?t>QZSE(#X#O{^lWF8Bz;ZxAcwO#y+f;W9bO$4R z?DXNu^*NVT%ft(tGD^b2!ougY#{RG464U$o*bH>;_NyhE%cEu7y2a5KA(1Z;z0mAP zX=Y}|WYC#KE&Ew~x$0>Px9dj2`-(k{-S&YA8X5#04dHk(EWHQK8k{>3dJdz+epe1|s>;_&ANAV9xcr ze{L)xmx`)tZ&TN!9UB`PBJ3-?uF|pThaBzji#X0kzrwXK;0o@ATY`hkH zesB)zCKI}wir=5FCWQpUqvV&BMP9FY#{NE|_+K?Y!bBZ|dZHB- z2JP5`Q!tW-+_rRtdzXk|%P|vMNjv6n773QOeqi^CX~KD&uS>ZvQxGD;Ca4%UX!G%} zgXR9$^}tr$d1tM*Xfp$|L`j5-g$!T2Xp1`s*Shqsmjw_nt{eg=&b=c1FcC8J=8d-O z?vrcEkp230B|33BY~E@E<^H*C``@!$@k4en@%e@!W@<>TrRL{$!yIA0S%@6lT%vz% z1tg#gXEs(hbI1qAY!U8$3ejB|o7by9e~=2j5`PfbG>rHKlda~``ay`UOftgVc) z9dooDgSH`;Lp2ts+E7z-1@SLm>fSzcXJOmqTR={Q5sRyPa349l?a*}|>_W^6@G(!>^VlEPW5_$m5n^Kq2lije% zV2Z$NIX^$I(&BySf=BsrcsBXni?F3NEKdeooG(h;jWr6r`I3xUk^oNN8KFzYFW}W4 zI+KT;0`oMD|dJ&{0ARy)6fnx@I&GATC90FMrzJX{a)+XQ$~PNQR? zjMfdDK{*L!C8Zz0=RGMBFQ56pA`t!;NXd;lmsh$w^h+ZmLK8~~gXny&Hm@uzYiD^M z=b2cA1EB*(jNr!QzqmuO9n|pyx8Is*#6J%JsB5kv&#Ug-E+5vzV6yESsEyPZ4zwfM zJ$@TJhqiYVKXH-iH|e_z@`g#fefNc&*g43k5Us7>Oc8vIJR0O_$^mIq@mf9~--RKHP7EO2hG& z*P{6ey09y^xcPZDKj)3VvE~up5ywctW-pKaH09w>A9|!XygRHO?!obZl>ISsr7>I& z)s46Kc<0kpi7IY2hJLHJ%LVVL&T@%Welp{~#&Ne3Sh8|QsIEZA2=a(Qr#4L_aVOTK zj_E7$K0VYICM*20ZA*52=Ys8M##$a#Jf+CWV*QOWl@OkdsQ6D^yrXY35&rq>ExBF_ z^R^GAQTv8{hsdo}7w04YIj{R(J8tzwGNY^o!Mz1kwRLm0y?P)EyoH2>#7kdTB?Knwlz?&D)^Ut-`2L zvC)rfPzZF4rh#vRDxtjh!{dqnUMQ~q*YDqx4y(I5gI`y(15X>e{;ARDaZB9q=^~fis3!!~DzmE$?)|JXM_A#wy&_7cGga`I6_kR4el1JfWHOZYFMM+0 zUNiS-akP6OBf6-=TtauDQGe15!koDn-J5ZYCS#dq;`ddwDrn?lelZDX1#@s=an~+L zztyD>*ZAJm-S%9CT;=;Oem5IUXWh>4o&f9qnx9W`fOp7%+-nGIEQCnVCw+S`UTRyU zHRuD{3Gm`K;JUVeKKfr~mmLpssA!t*n&9mYmia`Eyr(DV1UxHFBky&6|IJxuN?KXb zllq*(5Pq=T00IH{#;)KC<(Wqjxuc(&Az!E0d*0G=IXb(Mhtj`#lxqzV&1}G>75Ggj zAc}nK@9N&{Ac9DK)5LeYu^pJ21NwMtW`PtbTaCTA8s`Mm$eFcDOfRBK`a|x%iv$VE0?hb+js`4&>pt>r* zy*&#U(U|jdCmO|^(TfQo{~TKHtG|o<_kZh{v}yugc7ismqND@~$hThdJj0Hp;hU+m zU?X>RDv{Y-#;HD?k+1vs{jz$Gr-2zaGvAQ)5=xd-HPhPmDu#;puXQ2+J;DMC<5?vS z>}$RFTA=HTWYZ)~M92G=8f+#mF5Pg#(MXH)^Ix^ClB6=Eefv?R(-ik#=gpQ4{&Lfr zS2QROWi72C#_INIt5Pv9FWw2kd#0upr?*8#MK8YFBKX^N7M+RB43Z1}*(HPM#$aDM ziF*>xOoW9V;T;TOVACbu)AlQ)#+jBgs_z1E z)s7~vd`R$Z3YhBZi!IKyAb=A3ud=8plxxi+*nxY+s&u=k#`oS06s2mh962>L^<7&V zONLy1eLai+)5Ap%QtR8M}b9%F3S$vH9+mjL+@5i^*|#o z_LP0!lQIyB7=*rILtUV4Oab4!zcg>nmVw7*PXx^4i+YP^tlzAg5G-hJPG4Ek1xls0 zC_bdusL^&YsMu=7ZP^YR8(T?Rdx@N;@LM{!BSl$R*^4d(C-iUBKtokkTvXH>Oiaw? z-Ao(i4F917a#ITn=dC^@z)CPm_H9QW?1?y%d}JvEMr%PEuWPoZ?5~N<+ zGhOdK(LR6vXHe)NWxzT^j`{fSi$$*4KQxlB#%sw#!ah56lJ|C>!i7Q?%C*^auqMLZ zxnQ`jI3l#PwD6a^H1x$h z06T9VKUk_<(0tUmeCojuD(g9_uwnkNfA3<`f&0bRoBFk_D(LDG1qzcfz~M-V@^7w5 zlaXsQu<(8eroA6G_c_33;5SpdXPheXL;VP=;H=2-i%+Y=lD2yzm1j{~XVvGln?So< zar?16U3gAkv_T57B@WX`9o?Q=y7>=wYume3J*k<`^AU?zJgnRzZ}ZCa-r|7t=;6WV z|53TD%kku>*tmR(y*hiA0VCR zhby}iA!%(&qNJ)HMs&Esu zup9BtkSqbJmNzx?In>}A}{*Y=%#ohlP%fCGv8}kn&jME|6x3& zfXqy5jcV=K`NV#I(#FkBFJ=LRF>x(o#C~yc{f~S%M}_Fz3a}X|{P7$aapWjTUN%~7 zN(O|0w zC{ua%-}Fu@9WSM$Z-w<{xCn&v7%bY9;z-mNT~9U9Cq8?8wogtMMYJ+#7f-EStb0-y z9&TgG<#Wi`Vn9d~>!+Mq*Ez5ub>O_OT_OKRI>JvBt$Ahy}4^q^E+0ee$kk@#!VLHvnp+~ zSKF5?;dd(HKj*f5cTigR_qIU9-VB;#S$Vnfzk|8HzCI;2(6DTe8&MrkH$=)od_7-n za9N*|Pp4c4G{)zVsB%s0(2sTIiK(gmyJKI#tFhFvD>DiPP4;k9!@a^z*!dLWUa`qj zBaJL&zDQwowjedHk&F~K_TgWFbk=>Q|DmVj5caAb7hBaO&p$Z}^&T^u%#3q#%yNN& zP|J-vf{k0w?#!FgR3y3&?7M|zQNxmZI(jg8P=Sz(1;;nCu=sgvQ>!ud`-Lbh&rSu1K|t#`J^>QWE%lQgou)dp%DHeCn>6_Nk~t}b30m+5EB!-gt)m+ zRvQR`;Bw%T|Gf@y`a>!7s-06dY+s3`M`^HlB)FV>PNcJblPH89I;L+?aKVwC?pLaL zj!3hWBpDw?=5sGB|AqStrGn`dYkwDCo{sqW+W2XEbu&gTJ$5f)DA|-o!W*}UM_}pT8!_f@hNn=YGfnf0^6nF_-p6*Ya zpC7ONJD8ebc`YriI@qmz_9YMe|Dlrm^Gs((3s0g~^x~A#tbUFab2f$sbI4-SC`VBt ziG?LVIr`s-AMZnFogC9u5b*^^8|a5r?PpDK%8-;eCaKvrBB2ZtFjSI1g1zsT`_fgV zzoC33;g#|FspSBRXyqp-j+l3t6w*x+rpI?0#haUu@S`X6EsyJgnv2ywGeFVJ!MAF3 z(bK!bY7mAlS!Pxpg8YB`%;Uv(19Ix9-~}}=27=%T{tIOP+Yy7CWB0D*P2CeA0%9md zz{8qZb za#1CwF6YrJ&v6r9KyUt6n=QRVm;HU}O~?%+NkCRs)?1QKf8Si4tSh>@a<*30*{`;~ z-DL!_dpwIL&FmOgRNO?(?RaJQW#032-R0l!d%`fjd{`Fsb7Q$n%*u|q`uWK{MmAgS zlawH4Z~}5(K3PZ*deFndTGkb5KJ!&mr+4{7)qSeX{S#RJaJ%P)R=p(#$l&j( zx1~CnU98b3)nXI56gNgH5o(C)Ch;6F1nUaL|Kh$WS7K$#J_sK%FC>isURxv}XM3|IoDj(Fsm|lVR(9h8DIIM;IK1H)CKB9POjjrE$;S zMJg(nqn$M&O#SLK0AI+zA9rKO(-bKe%b{DpvYOq0hXhXr$t?J=%(Q_u$5 z?~mK2Y?z;>n#pjSot-@I`aP*oBdBLS%f=N1fUf(I)mOU7BH2`$Qqp;2))@UeM%UV8 zcl`8+TfvSD-e+ZP(T2YCBxvnbe^qw2q33RHpn|Zw$;=iXdLc@yz2Q&6F+Cx|o`37D zPq|wQy`U6@^RoDQTi#=LYpO@;dG@iy$G zss$x%YHPLj(smgcS*mKfK==`>5Nt$jjyxmf3T_Ts1!s!hWSaAZfW2mhD%?(o_bLQX zsvL*cu1{>*V$4~^YAO|;3v(V{ALp>`&|zJ;%8f;_K3`1tc;R{baXvqd@ZTA@-rn`j z#|n5~DQRj-JwH88w2jd%d6DCR5K%N@f7*q*`LL=DkIfYEB~H9O|D84pDpJ_kn1VSi z8VEGm-RjcP4d8I8XlNv>(b{(MKN#wgVxF*BVls#P-N%rWalxngg*_2b*xgrFqUE|J zjmqH{Vc?Esp+LKs87|?P<6BY}2;06XX>08>m|#wiNKjcAQ}%mp51#GxKv0w8hXHM2 z3h3P?c<_{xG0pp)*?D5&n7FuupbZwJt?Dv+UHu*Qe;D+Kh!<=yhoz)o55(Y$k)a!c zK{Dv3#TUP*%k`UZ+bS-B}F+YNs!qOTa(4Y%Pbniu&I1Vvc>evb z?_CRk<(r;X0dkWM1;G}rWM&v-qloaqwyUlGq7D`0<;{To)B80t5KB0SrN|#xUjA^^ z)Kvr;q53xsWetsiT9ZL7Fkwa$ydhKMKZ6BjwpeH3-YnZVa;y;D-?QNTh9~{wz!ddw zZiUOfD&EPWIO%jNRgvUM0q)-d3Pz$ZEpg@y9dl~;-z?{FkSh?G5c9J0Vt(LSoBPio z=+3z#NBVL)NiN{?3&v*!<)qg$#`lFs=sO(uGpC8_ti8Q;ygFW$1y>Pd;8AOzoNDi+ zVhQ?R@)c%g8a!k-FWvmgs;YWtz)lbUs{PHtR5k6Av#k8*N{ch=mofh5hl`gO7KF5IJTmX5C3BfdgZ1@rkFkdCgiCmSC~6weP>|0D@=Lu!*N@6!kTBsLd9yE zOdcahOkd(c5X^5%_P*^lWn%@YG64ZWzNU6gbbUPs7+>`fyRS?YUG#1b#F~IW0?hRF zSrkEQwz9I)fa8fyB2No^8 zJWDe%Kl}@*CX4QafVY z)!9i>w@B2{-%`g5qM<)vERyyB1$OlNcQ#-rCfelxs90KBegz-Xn~#i-r=_O;9rSBs zghrb1K-YDjJh&Lk{-D!(Koz67y*?<``-8*1`n6H57P`i3Y7Jv)+`heK>BC&8wFc(9 zi_~2DTimC!X5*x+=me7db5m+?Kf>`o9Dx7w3#y;YUEGbF%pfABpN!4Or0t9> a%v8*bOg$Zk&0g-Rf=Ej!h*yak2K^6lg_vjn diff --git a/skyquake/framework/style/img/osm_header_506x100.png b/skyquake/framework/style/img/osm_header_506x100.png index 7ece8458b6f66bb04978cb72dd939be0783bcada..4b7a344770dbefe9b9343f530ca62c017694bc23 100644 GIT binary patch literal 25054 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

n_qE11 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 B4i*3a diff --git a/skyquake/framework/utils/appConfiguration.js b/skyquake/framework/utils/appConfiguration.js new file mode 100644 index 000000000..28823097f --- /dev/null +++ b/skyquake/framework/utils/appConfiguration.js @@ -0,0 +1,42 @@ +/* + * + * Copyright 2017 RIFT.IO Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +import $ from 'jquery'; + +const configPromise = new Promise((resolve, reject) => { + $.ajax({ + url: '/app-config', + type: 'GET', + success: function (data) { + if (data.version.endsWith('.1')){ + data.version = '' + Date.now(); + } + resolve(data); + }, + error: function (error) { + console.log("There was an error getting config: ", error); + reject(error); + } + }).fail(function (xhr) { + console.log('There was an xhr error getting the config', xhr); + reject(xhr); + }); +}); + +module.exports = { + get: () => configPromise +}; \ No newline at end of file diff --git a/skyquake/framework/utils/roleConstants.js b/skyquake/framework/utils/roleConstants.js new file mode 100644 index 000000000..5ac4e641e --- /dev/null +++ b/skyquake/framework/utils/roleConstants.js @@ -0,0 +1,31 @@ +var c = {}; + +c.PLATFORM = { + OPER: "rw-rbac-platform:platform-oper", + ADMIN: "rw-rbac-platform:platform-admin", + SUPER: "rw-rbac-platform:super-admin" +} + +c.PROJECT = { + TYPE: { + "rw-project-mano:catalog-oper": "rw-project-mano", + "rw-project-mano:catalog-admin": "rw-project-mano", + "rw-project-mano:lcm-oper": "rw-project-mano", + "rw-project-mano:lcm-admin": "rw-project-mano", + "rw-project-mano:account-oper": "rw-project-mano", + "rw-project-mano:account-admin": "rw-project-mano", + "rw-project:project-oper": "rw-project", + "rw-project:project-admin": "rw-project" + }, + CATALOG_OPER: "rw-project-mano:catalog-oper", + CATALOG_ADMIN: "rw-project-mano:catalog-admin", + LCM_OPER: "rw-project-mano:lcm-oper", + LCM_ADMIN: "rw-project-mano:lcm-admin", + ACCOUNT_OPER: "rw-project-mano:account-oper", + ACCOUNT_ADMIN: "rw-project-mano:account-admin", + PROJECT_OPER: "rw-project:project-oper", + PROJECT_ADMIN: "rw-project:project-admin" + +} + +module.exports = c; diff --git a/skyquake/framework/utils/utils.js b/skyquake/framework/utils/utils.js index 7b93fd580..88ef939cb 100644 --- a/skyquake/framework/utils/utils.js +++ b/skyquake/framework/utils/utils.js @@ -15,13 +15,14 @@ * limitations under the License. * */ -//Login needs to be refactored. Too many cross dependencies -var AuthActions = require('../widgets/login/loginAuthActions.js'); -var $ = require('jquery'); -import rw from './rw.js'; +import AuthActions from '../widgets/login/loginAuthActions'; +import $ from 'jquery'; +import rw from './rw'; +import appConfiguration from './appConfiguration' +import SockJS from 'sockjs-client'; + var API_SERVER = rw.getSearchParams(window.location).api_server; let NODE_PORT = rw.getSearchParams(window.location).api_port || ((window.location.protocol == 'https:') ? 8443 : 8000); -var SockJS = require('sockjs-client'); var Utils = {}; @@ -29,25 +30,6 @@ Utils.DescriptorModelMeta = null; var INACTIVITY_TIMEOUT = 600000; -Utils.getInactivityTimeout = function() { - return new Promise(function(resolve, reject) { - $.ajax({ - url: '/inactivity-timeout', - type: 'GET', - success: function(data) { - resolve(data); - }, - error: function(error) { - console.log("There was an error getting the inactivity-timeout: ", error); - reject(error); - } - }).fail(function(xhr) { - console.log('There was an xhr error getting the inactivity-timeout', xhr); - reject(xhr); - }); - }); -}; - Utils.isMultiplexerLoaded = function() { if (window.multiplexer) { return true; @@ -75,14 +57,19 @@ Utils.setupMultiplexClient = function() { loadChecker(); }; -Utils.checkAndResolveSocketRequest = function(data, resolve, reject) { +Utils.checkAndResolveSocketRequest = function(data, resolve, reject, successCallback) { const checker = () => { if (!Utils.isMultiplexerLoaded()) { setTimeout(() => { checker(); }, 500); } else { - resolve(data.id); + if (!successCallback) { + resolve(data.id); + } else { + //resolve handled in callback + successCallback(data.id) + } } }; @@ -90,9 +77,8 @@ Utils.checkAndResolveSocketRequest = function(data, resolve, reject) { }; Utils.bootstrapApplication = function() { - var self = this; - return new Promise(function(resolve, reject) { - Promise.all([self.getInactivityTimeout()]).then(function(results) { + return new Promise((resolve, reject) => { + Promise.all([appConfiguration.get()]).then(function(results) { INACTIVITY_TIMEOUT = results[0]['inactivity-timeout']; resolve(); }, function(error) { @@ -129,8 +115,9 @@ Utils.getDescriptorModelMeta = function() { } Utils.addAuthorizationStub = function(xhr) { - var Auth = window.sessionStorage.getItem("auth"); - xhr.setRequestHeader('Authorization', 'Basic ' + Auth); + // NO-OP now that we are dealing with it on the server + // var Auth = window.sessionStorage.getItem("auth"); + // xhr.setRequestHeader('Authorization', 'Basic ' + Auth); }; Utils.getByteDataWithUnitPrefix = function(number, precision) { @@ -183,36 +170,30 @@ Utils.getPacketDataWithUnitPrefix = function(number, precision) { } } Utils.loginHash = "#/login"; -Utils.setAuthentication = function(username, password, cb) { - var self = this; - var AuthBase64 = btoa(username + ":" + password); - window.sessionStorage.setItem("auth", AuthBase64); - self.detectInactivity(); - $.ajax({ - url: '//' + window.location.hostname + ':' + window.location.port + '/check-auth?api_server=' + API_SERVER, - type: 'GET', - beforeSend: Utils.addAuthorizationStub, - success: function(data) { - //console.log("LoggingSource.getLoggingConfig success call. data=", data); - if (cb) { - cb(); - }; - }, - error: function(data) { - Utils.clearAuthentication(); - } - }); -} -Utils.clearAuthentication = function(callback) { + +Utils.clearAuthentication = function() { var self = this; window.sessionStorage.removeItem("auth"); AuthActions.notAuthenticated(); window.sessionStorage.setItem("locationRefHash", window.location.hash); - if (callback) { - callback(); - } else { - window.location.hash = Utils.loginHash; - } + var reloadURL = ''; + $.ajax({ + url: '//' + window.location.hostname + ':' + window.location.port + '/session?api_server=' + API_SERVER + '&hash=' + encodeURIComponent(window.location.hash), + type: 'DELETE', + success: function(data) { + console.log('User logged out'); + reloadURL = data['url'] + '?post_logout_redirect_uri=' + + window.location.protocol + '//' + + window.location.hostname + ':' + + window.location.port + + '/?api_server=' + API_SERVER; + + window.location.replace(reloadURL); + }, + error: function(data) { + console.log('Problem logging user out'); + } + }); } Utils.isNotAuthenticated = function(windowLocation, callback) { var self = this; @@ -322,4 +303,24 @@ Utils.parseError = (error) => { return displayMsg } +Utils.rpcError = (rpcResult) => { + try { + let info = JSON.parse(rpcResult); + let rpcError = info.body || info.errorMessage.body || info.errorMessage.error; + if (rpcError) { + if (typeof rpcError === 'string') { + const index = rpcError.indexOf('{'); + if (index >= 0) { + return JSON.parse(rpcError.substr(index)); + } + } else { + return rpcError; + } + } + } catch (e) { + } + console.log('invalid rpc error: ', rpcResult); + return null; +} + module.exports = Utils; diff --git a/skyquake/framework/widgets/button/button.scss b/skyquake/framework/widgets/button/button.scss index c972e147a..043a769ac 100644 --- a/skyquake/framework/widgets/button/button.scss +++ b/skyquake/framework/widgets/button/button.scss @@ -1,6 +1,6 @@ /* - * + * * Copyright 2016 RIFT.IO Inc * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -62,15 +62,18 @@ button{ ############################################################################ */ .SqButton { - align-items: center; + -ms-flex-align: center; + align-items: center; border-style: solid; border-radius: 3px; border-width: 0px; cursor: pointer; + display: -ms-inline-flexbox; display: inline-flex; font-size: 1rem; height: 50px; - justify-content: center; + -ms-flex-pack: center; + justify-content: center; margin: 0 10px; outline: none; padding: 0 15px; @@ -107,8 +110,9 @@ button{ /* Focus */ &:focus { - // box-shadow: $focus-shadow; - border: 1px solid red; + /* box-shadow: $focus-shadow;*/ + border: 1px solid; + border-color: darken($normalHoverBackground, 10%); } /* SIZES @@ -256,11 +260,14 @@ button{ fill: $primaryForeground; } } - - } - +.sqButtonGroup { + display: -ms-flexbox; + display: flex; + -ms-flex-pack: center; + justify-content: center; +} diff --git a/skyquake/framework/widgets/button/sq-button.jsx b/skyquake/framework/widgets/button/sq-button.jsx index ae9312845..8d0ec7702 100644 --- a/skyquake/framework/widgets/button/sq-button.jsx +++ b/skyquake/framework/widgets/button/sq-button.jsx @@ -36,17 +36,35 @@ export default class SqButton extends React.Component { Class += " is-disabled"; } return ( -

+
- {svgHTML} -
{label}
+ {svgHTML} +
{label}
+
+ ) + } +} + +export class ButtonGroup extends React.Component { + render() { + let className = "sqButtonGroup"; + if (this.props.className) { + className = `${className} ${this.props.className}` + } + return ( +
+ {this.props.children}
) } } + SqButton.defaultProps = { + onClick: function(e) { + console.log('Clicked') + }, icon: false, primary: false, disabled: false, diff --git a/skyquake/framework/widgets/components.js b/skyquake/framework/widgets/components.js index f38719aee..461a7834a 100644 --- a/skyquake/framework/widgets/components.js +++ b/skyquake/framework/widgets/components.js @@ -26,355 +26,7 @@ export default { // Histogram: Histogram, Multicomponent: require('./multicomponent/multicomponent.js'), Mixins: require('./mixins/ButtonEventListener.js'), - // Gauge: require('./gauge/gauge.js'), +// Gauge: require('./gauge/gauge.js'), Bullet: require('./bullet/bullet.js') }; -// require('../../assets/js/n3-line-chart.js'); -// var Gauge = require('../../assets/js/gauge-modified.js'); -// var bulletController = function($scope, $element) { -// this.$element = $element; -// this.vertical = false; -// this.value = 0; -// this.min = 0; -// this.max = 100; -// //this.range = this.max - this.min; -// //this.percent = (this.value - this.min) / this.range; -// this.displayValue = this.value; -// this.isPercent = (this.units == '')? true:false; -// this.bulletColor = "#6BB814"; -// this.fontsize = 28; -// this.radius = 4; -// this.containerMarginX = 0; -// this.containerMarginY = 0; -// this.textMarginX = 5; -// this.textMarginY = 42; -// this.bulletMargin = 0; -// this.width = 512; -// this.height = 64; -// this.markerX = -100; // puts it off screen unless set -// var self = this; -// if (this.isPercent) { -// this.displayValue + "%"; -// } -// $scope.$watch( -// function() { -// return self.value; -// }, -// function() { -// self.valueChanged(); -// } -// ); - -// } - -// bulletController.prototype = { - -// valueChanged: function() { -// var range = this.max - this.min; -// var normalizedValue = (this.value - this.min) / range; -// if (this.isPercent) { -// this.displayValue = String(Math.round(normalizedValue * 100)) + "%"; -// } else { -// this.displayValue = this.value; -// } -// // All versions of IE as of Jan 2015 does not support inline CSS transforms on SVG -// if (platform.name == 'IE') { -// this.bulletWidth = Math.round(100 * normalizedValue) + '%'; -// } else { -// this.bulletWidth = this.width - (2 * this.containerMarginX); -// var transform = 'scaleX(' + normalizedValue + ')'; -// var bullet = $(this.$element).find('.bullet2'); -// bullet.css('transform', transform); -// bullet.css('-webkit-transform', transform); -// } -// }, - -// markerChanged: function() { -// var range = this.max - this.min; -// var w = this.width - (2 * this.containerMarginX); -// this.markerX = this.containerMarginX + ((this.marker - this.min) / range ) * w; -// this.markerY1 = 7; -// this.markerY2 = this.width - 7; -// } -// } - -// angular.module('components', ['n3-line-chart']) -// .directive('rwBullet', function() { -// return { -// restrict : 'E', -// templateUrl: 'modules/views/rw.bullet.tmpl.html', -// bindToController: true, -// controllerAs: 'bullet', -// controller: bulletController, -// replace: true, -// scope: { -// min : '@?', -// max : '@?', -// value : '@', -// marker: '@?', -// units: '@?', -// bulletColor: '@?', -// label: '@?' -// } -// }; -// }) -// .directive('rwSlider', function() { -// var controller = function($scope, $element, $timeout) { -// // Q: is there a way to force attributes to be ints? -// $scope.min = $scope.min || "0"; -// $scope.max = $scope.max || "100"; -// $scope.step = $scope.step || "1"; -// $scope.height = $scope.height || "30"; -// $scope.orientation = $scope.orientation || 'horizontal'; -// $scope.tooltipInvert = $scope.tooltipInvert || false; -// $scope.percent = $scope.percent || false; -// $scope.kvalue = $scope.kvalue || false; -// $scope.direction = $scope.direction || "ltr"; -// $($element).noUiSlider({ -// start: parseInt($scope.value), -// step: parseInt($scope.step), -// orientation: $scope.orientation, -// range: { -// min: parseInt($scope.min), -// max: parseInt($scope.max) -// }, -// direction: $scope.direction -// }); -// //$(".no-Ui-target").Link('upper').to('-inline-
') -// var onSlide = function(e, value) { -// $timeout(function(){ -// $scope.value = value; -// }) - -// }; -// $($element).on({ -// change: onSlide, -// slide: onSlide, -// set: $scope.onSet({value: $scope.value}) -// }); -// var val = String(Math.round($scope.value)); -// if ($scope.percent) { -// val += "%" -// } else if ($scope.kvalue) { -// val += "k" -// } -// $($element).height($scope.height); -// if ($scope.tooltipInvert) { -// $($element).find('.noUi-handle').append("
" + val + "
"); -// } else { -// $($element).find('.noUi-handle').append("
" + val + "
"); -// } -// $scope.$watch('value', function(value) { -// var val = String(Math.round($scope.value)); -// if ($scope.percent) { -// val += "%" -// } else if($scope.kvalue) { -// val += "k" -// } -// $($element).val(value); -// $($element).find('.tooltip').html(val); -// if ($scope.tooltipInvert) { -// $($element).find('.tooltip').css('right', $($element).find('.tooltip').innerWidth() * -1); -// } else { -// $($element).find('.tooltip').css('left', $($element).find('.tooltip').innerWidth() * -1); -// } -// }); -// }; - -// return { -// restrict : 'E', -// template: '
', -// controller : controller, -// replace: true, -// scope: { -// min : '@', -// max : '@', -// width: '@', -// height: '@', -// step : '@', -// orientation : '@', -// tooltipInvert: '@', -// percent: '@', -// kvalue: '@?', -// onSet:'&?', -// direction: '@?', -// value:'=?' -// } -// }; -// }) -// .directive('rwGauge', function() { -// return { -// restrict: 'AE', -// template: '', -// replace: true, -// scope: { -// min: '@?', -// max: '@?', -// size: '@?', -// color: '@?', -// value: '@?', -// resize: '@?', -// isAggregate: '@?', -// units: '@?', -// valueFormat: '=?', -// width: '@?' -// }, -// bindToController: true, -// controllerAs: 'gauge', -// controller: function($scope, $element) { -// var self = this; -// this.gauge = null; -// this.min = this.min || 0; -// this.max = this.max || 100; -// this.nSteps = 14; -// this.size = this.size || 300; -// this.units = this.units || ''; -// $scope.width = this.width || 240; -// this.color = this.color || 'hsla(212, 57%, 50%, 1)'; -// if (!this.valueFormat) { -// if (this.max > 1000 || this.value) { -// self.valueFormat = { -// "int": 1, -// "dec": 0 -// }; -// } else { -// self.valueFormat = { -// "int": 1, -// "dec": 2 -// }; -// } -// } -// this.isAggregate = this.isAggregate || false; -// this.resize = this.resize || false; -// if (this.format == 'percent') { -// self.valueFormat = { -// "int": 3, -// "dec": 0 -// }; -// } -// $scope.$watch(function() { -// return self.max; -// }, function(n, o) { -// if(n !== o) { -// renderGauge(); -// } -// }); -// $scope.$watch(function() { -// return self.valueFormat; -// }, function(n, o) { -// if(n != 0) { -// renderGauge(); -// } -// }); -// $scope.$watch(function() { -// return self.value; -// }, function() { -// if (self.gauge) { -// // w/o rounding gauge will unexplainably thrash round. -// self.valueFormat = determineValueFormat(self.value); -// self.gauge.setValue(Math.ceil(self.value * 100) / 100); -// //self.gauge.setValue(Math.round(self.value)); -// } -// }); -// angular.element($element).ready(function() { -// console.log('rendering') -// renderGauge(); -// }) -// window.testme = renderGauge; -// function determineValueFormat(value) { - -// if (value > 999 || self.units == "%") { -// return { -// "int": 1, -// "dec": 0 -// } -// } - -// return { -// "int": 1, -// "dec": 2 -// } -// } -// function renderGauge(calcWidth) { -// if (self.max == self.min) { -// self.max = 14; -// } -// var range = self.max - self.min; -// var step = Math.round(range / self.nSteps); -// var majorTicks = []; -// for (var i = 0; i <= self.nSteps; i++) { -// majorTicks.push(self.min + (i * step)); -// }; -// var redLine = self.min + (range * 0.9); -// var config = { -// isAggregate: self.isAggregate, -// renderTo: angular.element($element)[0], -// width: calcWidth || self.size, -// height: calcWidth || self.size, -// glow: false, -// units: self.units, -// title: false, -// minValue: self.min, -// maxValue: self.max, -// majorTicks: majorTicks, -// valueFormat: determineValueFormat(self.value), -// minorTicks: 0, -// strokeTicks: false, -// highlights: [], -// colors: { -// plate: 'rgba(0,0,0,0)', -// majorTicks: 'rgba(15, 123, 182, .84)', -// minorTicks: '#ccc', -// title: 'rgba(50,50,50,100)', -// units: 'rgba(50,50,50,100)', -// numbers: '#fff', -// needle: { -// start: 'rgba(255, 255, 255, 1)', -// end: 'rgba(255, 255, 255, 1)' -// } -// } -// }; -// var min = config.minValue; -// var max = config.maxValue; -// var N = 1000; -// var increment = (max - min) / N; -// for (i = 0; i < N; i++) { -// var temp_color = 'rgb(0, 172, 238)'; -// if (i > 0.5714 * N && i <= 0.6428 * N) { -// temp_color = 'rgb(0,157,217)'; -// } else if (i >= 0.6428 * N && i < 0.7142 * N) { -// temp_color = 'rgb(0,142,196)'; -// } else if (i >= 0.7142 * N && i < 0.7857 * N) { -// temp_color = 'rgb(0,126,175)'; -// } else if (i >= 0.7857 * N && i < 0.8571 * N) { -// temp_color = 'rgb(0,122,154)'; -// } else if (i >= 0.8571 * N && i < 0.9285 * N) { -// temp_color = 'rgb(0,96,133)'; -// } else if (i >= 0.9285 * N) { -// temp_color = 'rgb(0,80,112)'; -// } -// config.highlights.push({ -// from: i * increment, -// to: increment * (i + 2), -// color: temp_color -// }) -// } -// var updateSize = _.debounce(function() { -// config.maxValue = self.max; -// var clientWidth = self.parentNode.parentNode.clientWidth / 2; -// var calcWidth = (300 > clientWidth) ? clientWidth : 300; -// self.gauge.config.width = self.gauge.config.height = calcWidth; -// self.renderGauge(calcWidth); -// }, 500); -// if (self.resize) $(window).resize(updateSize) -// if (self.gauge) { -// self.gauge.updateConfig(config); -// } else { -// self.gauge = new Gauge(config); -// self.gauge.draw(); -// } -// }; -// }, -// } -// }); diff --git a/skyquake/framework/widgets/form_controls/formControls.jsx b/skyquake/framework/widgets/form_controls/formControls.jsx new file mode 100644 index 000000000..e50fb7ef1 --- /dev/null +++ b/skyquake/framework/widgets/form_controls/formControls.jsx @@ -0,0 +1,113 @@ +import './formControls.jsx'; + +import React from 'react' +import SelectOption from 'widgets/form_controls/selectOption.jsx'; +import imgAdd from '../../../node_modules/open-iconic/svg/plus.svg' +import imgRemove from '../../../node_modules/open-iconic/svg/trash.svg' +import TextInput from 'widgets/form_controls/textInput.jsx'; +import Input from 'widgets/form_controls/input.jsx'; + +export class FormSection extends React.Component { + render() { + let className = 'FormSection ' + this.props.className; + let html = ( +
+
+ {this.props.title} +
+
+ {this.props.children} +
+
+ ); + return html; + } +} + +FormSection.defaultProps = { + className: '' +} + +/** + * AddItemFn: + */ +export class InputCollection extends React.Component { + constructor(props) { + super(props); + this.collection = props.collection; + } + buildTextInput(onChange, v, i) { + return ( + + ) + } + buildSelectOption(initial, options, onChange, v, i) { + return ( + + ); + } + showInput() { + + } + render() { + const props = this.props; + let inputType; + let className = "InputCollection"; + if (props.className) { + className = `${className} ${props.className}`; + } + if (props.type == 'select') { + inputType = this.buildSelectOption.bind(this, props.initial, props.options, props.onChange); + } else { + inputType = this.buildTextInput.bind(this, props.onChange) + } + let html = ( +
+ {props.collection.map((v,i) => { + return ( +
+ {inputType(v, i)} + { + props.readonly ? null : Remove} +
+ ) + })} + { props.readonly ? null : Add} +
+ ); + return html; + } +} + +InputCollection.defaultProps = { + input: Input, + collection: [], + onChange: function(i, e) { + console.log(` + Updating with: ${e.target.value} + At index of: ${i} + `) + }, + AddItemFn: function(e) { + console.log(`Adding a new item to collection`) + }, + RemoveItemFn: function(i, e) { + console.log(`Removing item from collection at index of: ${i}`) + } +} diff --git a/skyquake/framework/widgets/form_controls/formControls.scss b/skyquake/framework/widgets/form_controls/formControls.scss index 4a8843501..ad95add98 100644 --- a/skyquake/framework/widgets/form_controls/formControls.scss +++ b/skyquake/framework/widgets/form_controls/formControls.scss @@ -1,5 +1,5 @@ /* - * + * * Copyright 2016 RIFT.IO Inc * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,7 +15,7 @@ * limitations under the License. * */ -@import 'style/_colors.scss'; +@import '../../style/_colors.scss'; .sqTextInput { display: -ms-flexbox; @@ -32,9 +32,9 @@ color:$darker-gray; text-transform:uppercase; } - input, .readonly, textarea { + input, textarea { height: 35px; - box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.39), 0 -1px 1px #ffffff, 0 1px 0 #ffffff; + /* box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.39), 0 -1px 1px #ffffff, 0 1px 0 #ffffff;*/ font-size: 1rem; display: block; background: white !important; @@ -49,6 +49,7 @@ .readonly { line-height: 35px; box-shadow:none; + background:none !important; } textarea { -ms-flex-align: stretch; @@ -57,4 +58,107 @@ border:0px; height: 100%; } + &.checkbox { + -ms-flex-direction:row; + flex-direction:row; + -ms-flex-align:center; + align-items:center; + margin-bottom:0; + >span { + -ms-flex-order: 1; + order: 1; + padding-left:1rem; + } + >input { + -ms-flex-order: 0; + order: 0; + + box-shadow:none; + height:25px; + } + } + .invalid { + color: red; + font-weight:strong; + } + input:invalid { + border: 2px solid red; + &:after { + content: 'Invalid Value' + } + } +} + +.sqCheckBox { + display:-ms-flexbox; + display:flex; + label { + display:-ms-flexbox; + display:flex; + -ms-flex-align: center; + align-items: center; + input { + box-shadow: none; + height: auto; + margin: 0 0.25rem; + } + } +} + +.FormSection { + &-title { + color: #000; + background: lightgray; + padding: 0.5rem; + border-top: 1px solid #f1f1f1; + border-bottom: 1px solid #f1f1f1; + } + &-body { + padding: 0.5rem 0.75rem; + } + label { + -ms-flex: 1 0; + flex: 1 0; + } + /* label {*/ + /* display: -ms-flexbox;*/ + /* display: flex;*/ + /* -ms-flex-direction: column;*/ + /* flex-direction: column;*/ + /* width: 100%;*/ + /* margin: 0.5rem 0;*/ + /* -ms-flex-align: start;*/ + /* align-items: flex-start;*/ + /* -ms-flex-pack: start;*/ + /* justify-content: flex-start;*/ + /* }*/ + select { + font-size: 1rem; + min-width: 75%; + height: 35px; + } +} + + + + +.InputCollection { + display:-ms-flexbox; + display:flex; + -ms-flex-wrap: nowrap; + flex-wrap: nowrap; + -ms-flex-align: center; + align-items: center; + button { + padding: 0.25rem; + height: 1.5rem; + font-size: 0.75rem; + } + select { + min-width: 100%; + } + margin-bottom:0.5rem; + &-wrapper { + + } } diff --git a/skyquake/framework/widgets/form_controls/input.jsx b/skyquake/framework/widgets/form_controls/input.jsx new file mode 100644 index 000000000..ca31c13cd --- /dev/null +++ b/skyquake/framework/widgets/form_controls/input.jsx @@ -0,0 +1,134 @@ +/* + * + * Copyright 2016 RIFT.IO Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +import './formControls.scss'; +import SelectOption from 'widgets/form_controls/selectOption.jsx'; +import CheckSVG from '../../../node_modules/open-iconic/svg/check.svg' +import React, {Component} from 'react'; + +export default class Input extends Component { + render() { + let {label, value, defaultValue, ...props} = this.props; + let inputProperties = { + value: value + } + let isRequired; + let inputType; + let tester = null; + let className = `sqTextInput ${props.className}`; + + if(this.props.required) { + isRequired = * + } + if (defaultValue) { + inputProperties.defaultValue = defaultValue; + } + if (props.pattern) { + inputProperties.pattern = props.pattern; + tester = new RegExp(props.pattern); + } + if(props.hasOwnProperty('type') && (props.type.toLowerCase() == 'checkbox')) { + inputProperties.checked = props.checked; + className = `${className} checkbox`; + } + if (value == undefined) { + value = defaultValue; + } + switch(props.type) { + case 'textarea': + inputType =