diff --git a/Source/Applications/SystemCenter/Controllers/OpenXDA/OpenXDAControllers.cs b/Source/Applications/SystemCenter/Controllers/OpenXDA/OpenXDAControllers.cs index 839e55193..704a6ef26 100644 --- a/Source/Applications/SystemCenter/Controllers/OpenXDA/OpenXDAControllers.cs +++ b/Source/Applications/SystemCenter/Controllers/OpenXDA/OpenXDAControllers.cs @@ -338,7 +338,7 @@ public class ApplicationNodeController : ModelController { } [RoutePrefix("api/OpenXDA/MeterDataQualitySummary")] public class MeterDataQualitySummaryController : ModelController { } - [RoutePrefix("api/OpenXDA/remoteXDAInstance")] + [RoutePrefix("api/OpenXDA/remoteXDAInstance"), HttpEditionFilter(Edition.Enterprise)] public class RemoteXDAInstanceController : ModelController { #region [Properties] diff --git a/Source/Applications/SystemCenter/Controllers/SystemCenter/AccessLogController.cs b/Source/Applications/SystemCenter/Controllers/SystemCenter/AccessLogController.cs index 2f9fdf9f3..51fd7a340 100644 --- a/Source/Applications/SystemCenter/Controllers/SystemCenter/AccessLogController.cs +++ b/Source/Applications/SystemCenter/Controllers/SystemCenter/AccessLogController.cs @@ -27,6 +27,7 @@ using GSF.Data.Model; using GSF.Security.Model; using GSF.Web.Model; +using openXDA.Configuration; using System; using System.Collections.Generic; using System.Data; @@ -37,7 +38,7 @@ namespace SystemCenter.Controllers { - [RoutePrefix("api/SystemCenter/AccessLog")] + [RoutePrefix("api/SystemCenter/AccessLog"), HttpEditionFilter(Edition.Enterprise)] public class SystemCenterAccessLogController : ApiController { private string Connection { get; } = "systemSettings"; diff --git a/Source/Applications/SystemCenter/SystemCenter.csproj b/Source/Applications/SystemCenter/SystemCenter.csproj index c01879dc9..875f981a8 100644 --- a/Source/Applications/SystemCenter/SystemCenter.csproj +++ b/Source/Applications/SystemCenter/SystemCenter.csproj @@ -484,6 +484,7 @@ PreserveNewest + PreserveNewest @@ -551,6 +552,7 @@ + diff --git a/Source/Applications/SystemCenter/wwwroot/Images/GiantLogo.png b/Source/Applications/SystemCenter/wwwroot/Images/GiantLogo.png new file mode 100644 index 000000000..30a18ceb6 Binary files /dev/null and b/Source/Applications/SystemCenter/wwwroot/Images/GiantLogo.png differ diff --git a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/CommonComponents/EditionLockPage.tsx b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/CommonComponents/EditionLockPage.tsx new file mode 100644 index 000000000..20a02de03 --- /dev/null +++ b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/CommonComponents/EditionLockPage.tsx @@ -0,0 +1,69 @@ +//****************************************************************************************************** +// EditionLockPage.tsx - Gbtc +// +// Copyright © 2025, Grid Protection Alliance. All Rights Reserved. +// +// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See +// the NOTICE file distributed with this work for additional information regarding copyright ownership. +// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this +// file except in compliance with the License. You may obtain a copy of the License at: +// +// http://opensource.org/licenses/MIT +// +// Unless agreed to in writing, the subject software distributed under the License is distributed on an +// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the +// License for the specific language governing permissions and limitations. +// +// Code Modification History: +// ---------------------------------------------------------------------------------------------------- +// 01/14/2025 - Gabriel Santos +// Generated original version of source code. +// +//****************************************************************************************************** + +import * as React from 'react'; +import { useAppSelector, useAppDispatch } from '../hooks'; +import { ConfigSlice } from '../Store/Store'; +import { ServerErrorIcon, LoadingScreen } from '@gpa-gemstone/react-interactive'; + +interface IProps { + EditionRequirement?: 'Enterprise' | 'Base' +} + +const EditionLockPage: React.FunctionComponent = (props) => { + let dispatch = useAppDispatch(); + const configStatus = useAppSelector(ConfigSlice.XDAConfigStatus); + const config = useAppSelector(ConfigSlice.XDAConfig); + + React.useEffect(() => { + if (configStatus == 'unintiated' || configStatus == 'changed') + dispatch(ConfigSlice.FetchXDAConfig()); + }, [configStatus]); + + if (config.EditionStatus[props.EditionRequirement ?? 'Enterprise'] ?? false) return <>{props.children}; + + // Note: Using loading screen this way is intentional, we don't want the error screen to begin rendering before we check the edition + return ( +
+ { configStatus === 'loading' ? : +
+
+ +
+
+ +
+
+

+ Click here if you believe you are receiving this message in error or would like to inquire about openXDA {props.EditionRequirement ?? 'Enterprise'} Edition. +

+
+
+ } +
+ ) +} + +export default EditionLockPage; + diff --git a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/CommonComponents/EditionTooltip.tsx b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/CommonComponents/EditionTooltip.tsx index b396b8768..dc1d1b2f1 100644 --- a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/CommonComponents/EditionTooltip.tsx +++ b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/CommonComponents/EditionTooltip.tsx @@ -42,6 +42,14 @@ const EditionTooltip: React.FunctionComponent = (props) => { const [inSpecifiedEdition, setInSpecifiedEdition] = React.useState(false); + const message: string = React.useMemo(() => { + switch (configStatus) { + case 'error': return "Unable to retrieve edition status."; + case 'idle': return `${props.FeatureName} is only available in ${props.EditionRequirement ?? 'Enterprise'} Edition.`; + default: return "Validating License..." + } + }, [configStatus]); + React.useEffect(() => { const result = config.EditionStatus[props.EditionRequirement ?? 'Enterprise'] ?? false; setInSpecifiedEdition(result); @@ -55,14 +63,6 @@ const EditionTooltip: React.FunctionComponent = (props) => { if (inSpecifiedEdition) return null; - const message: string = React.useMemo(() => { - switch (configStatus) { - case 'error': return "Unable to retrieve edition status."; - case 'idle': return `${props.FeatureName} is only available in ${props.EditionRequirement ?? 'Enterprise'} Edition.`; - default: return "Retrieving edition status..." - } - }, [configStatus]); - return ( {message} diff --git a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/RemoteXDA/RemoteXDAInstanceMain.tsx b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/RemoteXDA/RemoteXDAInstanceMain.tsx index 20465347a..2abfa64d3 100644 --- a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/RemoteXDA/RemoteXDAInstanceMain.tsx +++ b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/RemoteXDA/RemoteXDAInstanceMain.tsx @@ -29,6 +29,7 @@ import { GenericController, Modal } from '@gpa-gemstone/react-interactive'; import { CrossMark } from '@gpa-gemstone/gpa-symbols'; import { RemoteXDAInstanceForm, BlankRemoteXDAInstance } from './RemoteXDAInstanceForm'; import GenericByPage from '../CommonComponents/GenericByPage'; +import EditionLockPage from '../CommonComponents/EditionLockPage'; import { SystemCenter } from '../global'; declare var homePath: string; @@ -51,58 +52,60 @@ const RemoteXDAInstanceMain: Application.Types.iByComponent = (props) => { navigate(`${homePath}index.cshtml?name=RemoteXDAInstance&ID=${item.row.ID}`); } - return <> - - ControllerPath={controllerPath} - RefreshData={refreshCount} - DefaultSortKey='Name' - PagingID='RemoteXDAInstanceMain' - OnClick={(item) => { handleSelect(item); }} - Columns={fieldCols} - DefaultSearchAscending={true} - DefaultSearchKey='Name' - > - - { - if (conf) - RemoteXDAInstanceController.DBAction("POST", formInstance).done(() => { - refreshData(x => x + 1); - }); - setShowNew(false); - }} - DisableConfirm={newInstErrors.length > 0} - ShowX={true} - ConfirmShowToolTip={newInstErrors.length > 0} - ConfirmToolTipContent={newInstErrors.map((t, i) => -

{CrossMark} {t}

- )}> - -
- { /* Portal endpoint for inner modal for new remote instance connection */ } -
- - + return ( + + + ControllerPath={controllerPath} + RefreshData={refreshCount} + DefaultSortKey='Name' + PagingID='RemoteXDAInstanceMain' + OnClick={(item) => { handleSelect(item); }} + Columns={fieldCols} + DefaultSearchAscending={true} + DefaultSearchKey='Name' + > + + { + if (conf) + RemoteXDAInstanceController.DBAction("POST", formInstance).done(() => { + refreshData(x => x + 1); + }); + setShowNew(false); + }} + DisableConfirm={newInstErrors.length > 0} + ShowX={true} + ConfirmShowToolTip={newInstErrors.length > 0} + ConfirmToolTipContent={newInstErrors.map((t, i) => +

{CrossMark} {t}

+ )}> + +
+ { /* Portal endpoint for inner modal for new remote instance connection */} +
+ + + ); } export default RemoteXDAInstanceMain; diff --git a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/UserStatistics/UserStatistics.tsx b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/UserStatistics/UserStatistics.tsx index 8f6e7a19f..94e36fc3a 100644 --- a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/UserStatistics/UserStatistics.tsx +++ b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/UserStatistics/UserStatistics.tsx @@ -30,6 +30,7 @@ import * as _ from 'lodash'; import moment from 'moment'; import { useAppDispatch, useAppSelector } from '../hooks'; import { ApplicationNodeSlice } from '../Store/Store'; +import EditionLockPage from '../CommonComponents/EditionLockPage'; interface Aggregate { Date: string, @@ -171,7 +172,7 @@ const UserStatistics: Application.Types.iByComponent = (props) => { } return ( -
+
@@ -269,9 +270,8 @@ const UserStatistics: Application.Types.iByComponent = (props) => {
-
- -
+
+
) }