From bd78b9d1445a04c1873c1b0b5a21e3637987840a Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Fri, 31 Jan 2025 16:58:49 -0500 Subject: [PATCH] feat(capabilities): functionality to disable UI capabilities for different contexts (#1553) --- src/app/Archives/Archives.tsx | 45 +++++++++++++++++------- src/app/Events/EventTemplates.tsx | 36 +++++++++++-------- src/app/Rules/Rules.tsx | 17 ++++++--- src/app/Shared/Services/Capabilities.tsx | 27 ++++++++++++++ src/app/index.tsx | 29 ++++++++------- 5 files changed, 109 insertions(+), 45 deletions(-) create mode 100644 src/app/Shared/Services/Capabilities.tsx diff --git a/src/app/Archives/Archives.tsx b/src/app/Archives/Archives.tsx index 46cbf2b51..e22722342 100644 --- a/src/app/Archives/Archives.tsx +++ b/src/app/Archives/Archives.tsx @@ -16,6 +16,7 @@ import { BreadcrumbPage } from '@app/BreadcrumbPage/BreadcrumbPage'; import { ArchivedRecordingsTable } from '@app/Recordings/ArchivedRecordingsTable'; import { Target, UPLOADS_SUBDIRECTORY } from '@app/Shared/Services/api.types'; +import { CapabilitiesContext } from '@app/Shared/Services/Capabilities'; import { ServiceContext } from '@app/Shared/Services/Services'; import { useSubscriptions } from '@app/utils/hooks/useSubscriptions'; import { getActiveTab, switchTab } from '@app/utils/utils'; @@ -65,6 +66,7 @@ export const Archives: React.FC = ({ ...props }) => { const { search, pathname } = useLocation(); const navigate = useNavigate(); const context = React.useContext(ServiceContext); + const capabilities = React.useContext(CapabilitiesContext); const addSubscription = useSubscriptions(); const activeTab = React.useMemo(() => { @@ -73,6 +75,33 @@ export const Archives: React.FC = ({ ...props }) => { const [archiveEnabled, setArchiveEnabled] = React.useState(false); + const uploadTargetAsObs = React.useMemo(() => of(uploadAsTarget), []); + + const tabs = React.useMemo(() => { + const arr: JSX.Element[] = []; + if (archiveEnabled) { + arr.push( + All Targets}> + + , + ); + arr.push( + All Archives}> + + , + ); + + if (capabilities.fileUploads) { + arr.push( + Uploads}> + + , + ); + } + } + return arr; + }, [capabilities.fileUploads, archiveEnabled, uploadTargetAsObs]); + React.useEffect(() => { addSubscription(context.api.isArchiveEnabled().subscribe(setArchiveEnabled)); }, [context.api, addSubscription, setArchiveEnabled]); @@ -83,20 +112,10 @@ export const Archives: React.FC = ({ ...props }) => { [navigate, pathname, search], ); - const uploadTargetAsObs = React.useMemo(() => of(uploadAsTarget), []); - const cardBody = React.useMemo(() => { - return archiveEnabled ? ( + return tabs.length > 0 ? ( - All Targets}> - - - All Archives}> - - - Uploads}> - - + {tabs} ) : ( @@ -107,7 +126,7 @@ export const Archives: React.FC = ({ ...props }) => { /> ); - }, [archiveEnabled, activeTab, uploadTargetAsObs, onTabSelect]); + }, [tabs, activeTab, onTabSelect]); return ( diff --git a/src/app/Events/EventTemplates.tsx b/src/app/Events/EventTemplates.tsx index 11a66b742..d5ba4a248 100644 --- a/src/app/Events/EventTemplates.tsx +++ b/src/app/Events/EventTemplates.tsx @@ -23,6 +23,7 @@ import { FUpload, MultiFileUpload, UploadCallbacks } from '@app/Shared/Component import { LoadingView } from '@app/Shared/Components/LoadingView'; import { LoadingProps } from '@app/Shared/Components/types'; import { EventTemplate, NotificationCategory, Target } from '@app/Shared/Services/api.types'; +import { CapabilitiesContext } from '@app/Shared/Services/Capabilities'; import { ServiceContext } from '@app/Shared/Services/Services'; import { useSubscriptions } from '@app/utils/hooks/useSubscriptions'; import { portalRoot, sortResources, TableColumn, toPath } from '@app/utils/utils'; @@ -91,6 +92,7 @@ export interface EventTemplatesProps {} export const EventTemplates: React.FC = () => { const context = React.useContext(ServiceContext); + const capabilities = React.useContext(CapabilitiesContext); const navigate = useNavigate(); const { t } = useCryostatTranslation(); @@ -364,20 +366,26 @@ export const EventTemplates: React.FC = () => { /> - - - - - - + {capabilities.fileUploads ? ( + <> + + + + + + + + ) : ( + <> + )} = () => { const context = React.useContext(ServiceContext); + const capabilities = React.useContext(CapabilitiesContext); const navigate = useNavigate(); const addSubscription = useSubscriptions(); const { t } = useCryostatTranslation(); @@ -396,11 +398,15 @@ export const RulesTable: React.FC = () => { {t('CREATE')} - - - + {capabilities.fileUploads ? ( + + + + ) : ( + <> + )} {ruleToWarn ? ( = () => { handleWarningModalClose, cleanRuleEnabled, setCleanRuleEnabled, + capabilities.fileUploads, ], ); diff --git a/src/app/Shared/Services/Capabilities.tsx b/src/app/Shared/Services/Capabilities.tsx new file mode 100644 index 000000000..56d9d0b6c --- /dev/null +++ b/src/app/Shared/Services/Capabilities.tsx @@ -0,0 +1,27 @@ +/* + * Copyright The Cryostat Authors. + * + * 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 * as React from 'react'; + +interface Capabilities { + fileUploads: boolean; +} +const defaultCapabilities: Capabilities = { + fileUploads: true, +}; + +const CapabilitiesContext: React.Context = React.createContext(defaultCapabilities); + +export { Capabilities, CapabilitiesContext, defaultCapabilities }; diff --git a/src/app/index.tsx b/src/app/index.tsx index 1d1baf75a..0693a5eb6 100644 --- a/src/app/index.tsx +++ b/src/app/index.tsx @@ -29,19 +29,22 @@ import * as React from 'react'; import { Provider } from 'react-redux'; import { BrowserRouter as Router } from 'react-router-dom-v5-compat'; import { JoyrideProvider } from './Joyride/JoyrideProvider'; +import { CapabilitiesContext, defaultCapabilities } from './Shared/Services/Capabilities'; export const App: React.FC = () => ( - - - - - - - - - - - - - + + + + + + + + + + + + + + + );