diff --git a/src/app/Caches/CacheMetrics.tsx b/src/app/Caches/CacheMetrics.tsx index b50b2863..f0ab8028 100644 --- a/src/app/Caches/CacheMetrics.tsx +++ b/src/app/Caches/CacheMetrics.tsx @@ -1,10 +1,14 @@ -import React, { useEffect, useState } from 'react'; +import React, { useState } from 'react'; import { + Button, + ButtonVariant, Card, CardBody, CardTitle, EmptyState, + EmptyStateActions, EmptyStateBody, + EmptyStateFooter, EmptyStateHeader, EmptyStateIcon, EmptyStateVariant, @@ -20,7 +24,7 @@ import { TextVariants } from '@patternfly/react-core'; import displayUtils from '@services/displayUtils'; -import { CubesIcon } from '@patternfly/react-icons'; +import { CubesIcon, ExclamationCircleIcon } from '@patternfly/react-icons'; import { QueryMetrics } from '@app/Caches/Query/QueryMetrics'; import { DataDistributionChart } from './DataDistributionChart'; import { PopoverHelp } from '@app/Common/PopoverHelp'; @@ -32,32 +36,24 @@ import { ConsoleServices } from '@services/ConsoleServices'; import { useConnectedUser } from '@app/services/userManagementHook'; import { ConsoleACL } from '@services/securityService'; import { CacheLifecycle } from '@app/Caches/CacheLifecycle'; +import { global_danger_color_200 } from '@patternfly/react-tokens'; +import { Link } from 'react-router-dom'; const CacheMetrics = (props: { cacheName: string; display: boolean }) => { + const { t } = useTranslation(); const { connectedUser } = useConnectedUser(); const { cache, error, loading } = useCacheDetail(); const [stats, setStats] = useState(cache.stats); - const [displayQueryStats, setDisplayQueryStats] = useState(false); - const [displayDataDistribution, setDisplayDataDistribution] = useState(false); - const [memory, setMemory] = useState(undefined); - const { t } = useTranslation(); - const brandname = t('brandname.brandname'); - - useEffect(() => { - if (ConsoleServices.security().hasConsoleACL(ConsoleACL.ADMIN, connectedUser)) { - // Data distribution is for admin only - setDisplayDataDistribution(true); - const loadMemory = cache.memory; - if (loadMemory) { - setMemory(loadMemory.storage_type == 'OFF_HEAP' ? StorageType.OFF_HEAP : StorageType.HEAP); - } else { - setMemory(StorageType.HEAP); - } + const [displayQueryStats, setDisplayQueryStats] = useState(cache.queryable!); + const [displayDataDistribution, setDisplayDataDistribution] = useState( + ConsoleServices.security().hasConsoleACL(ConsoleACL.ADMIN, connectedUser) + ); + const memory = () => { + if (cache.memory) { + return cache.memory.storage_type == 'OFF_HEAP' ? StorageType.OFF_HEAP : StorageType.HEAP; } - - setStats(cache.stats); - setDisplayQueryStats(cache.queryable); - }, [cache, error]); + return StorageType.HEAP; + }; const buildOperationsPerformanceCard = () => { if (!stats) { @@ -180,7 +176,7 @@ const CacheMetrics = (props: { cacheName: string; display: boolean }) => { return ''; } let content; - if (memory === StorageType.OFF_HEAP) { + if (memory() === StorageType.OFF_HEAP) { content = ( @@ -242,18 +238,59 @@ const CacheMetrics = (props: { cacheName: string; display: boolean }) => { }; if (!props.display) { - return ; + return <>; + } + + if (loading && error.length == 0) { + return ( + + + + } + /> + + + + ); } - if (!stats || loading) { - return ; + if (error.length > 0) { + return ( + + + + {`An error occurred while retrieving stats ${props.cacheName}`}} + icon={} + headingLevel="h2" + /> + {error} + + + + + + + + + + + ); } - if (!stats.enabled) { + if (stats && !stats.enabled) { return ( {t('caches.cache-metrics.metrics-title')}} + titleText={t('caches.cache-metrics.metrics-title')} icon={} headingLevel="h5" /> @@ -273,8 +310,8 @@ const CacheMetrics = (props: { cacheName: string; display: boolean }) => { {buildOperationsPerformanceCard()} {displayDataDistribution && {buildDataDistribution()}} - - + + {buildQueryStats()} diff --git a/src/app/Caches/Create/AdvancedOptionsConfigurator.tsx b/src/app/Caches/Create/AdvancedOptionsConfigurator.tsx index aca80a04..1397cba8 100644 --- a/src/app/Caches/Create/AdvancedOptionsConfigurator.tsx +++ b/src/app/Caches/Create/AdvancedOptionsConfigurator.tsx @@ -6,7 +6,9 @@ import { Grid, GridItem, HelperText, - HelperTextItem, InputGroup, InputGroupItem, + HelperTextItem, + InputGroup, + InputGroupItem, Switch, TextInput } from '@patternfly/react-core'; @@ -135,68 +137,68 @@ const AdvancedOptionsConfigurator = (props: { cacheManager: CacheManager }) => { - + } + > + handleConcurrencyLevel(value)} + aria-label="concurrency-level-input" + data-cy="concurencyLevel" /> - } - > - handleConcurrencyLevel(value)} - aria-label="concurrency-level-input" - data-cy="concurencyLevel" - /> - + - - } - > - - - - - handleLockAcquisitionTimeout(value)} - aria-label="lock-acquisition-timeout-input" - data-cy="lockTimeout" - /> - - - setLockAcquisitionTimeoutUnit(value)} - /> - - - - - + + } + > + + + + + handleLockAcquisitionTimeout(value)} + aria-label="lock-acquisition-timeout-input" + data-cy="lockTimeout" + /> + + + setLockAcquisitionTimeoutUnit(value)} + /> + + + + + diff --git a/src/app/Caches/Create/AdvancedTuning/BackupsCofigurationTuning.tsx b/src/app/Caches/Create/AdvancedTuning/BackupsCofigurationTuning.tsx index c7e14280..9b908e49 100644 --- a/src/app/Caches/Create/AdvancedTuning/BackupsCofigurationTuning.tsx +++ b/src/app/Caches/Create/AdvancedTuning/BackupsCofigurationTuning.tsx @@ -26,7 +26,9 @@ const BackupsConfigurationTuning = () => { ); const [mergePolicy, setMergePolicy] = useState(configuration.advanced.backupSetting?.mergePolicy); const [maxCleanupDelay, setMaxCleanupDelay] = useState(configuration.advanced.backupSetting?.maxCleanupDelay); - const [maxCleanupDelayUnit, setMaxCleanupDelayUnit] = useState(configuration.advanced.backupSetting?.maxCleanupDelayUnit); + const [maxCleanupDelayUnit, setMaxCleanupDelayUnit] = useState( + configuration.advanced.backupSetting?.maxCleanupDelayUnit + ); const [tombstoneMapSize, setTombstoneMapSize] = useState(configuration.advanced.backupSetting?.tombstoneMapSize); useEffect(() => { @@ -69,9 +71,7 @@ const BackupsConfigurationTuning = () => { - setMergePolicy(val === ''? undefined! : val) - } + onChange={(_event, val) => setMergePolicy(val === '' ? undefined! : val)} aria-label="merge-policy-input" /> @@ -87,12 +87,14 @@ const BackupsConfigurationTuning = () => { /> } > - + { value={tombstoneMapSize} onChange={(_event, val) => { const parsedVal = parseInt(val); - setTombstoneMapSize(isNaN(parsedVal) ? undefined! : parsedVal) + setTombstoneMapSize(isNaN(parsedVal) ? undefined! : parsedVal); }} aria-label="tombstone-map-size-input" /> diff --git a/src/app/Caches/Create/AdvancedTuning/BackupsSiteConfigurator.tsx b/src/app/Caches/Create/AdvancedTuning/BackupsSiteConfigurator.tsx index af4807b4..c9586ee8 100644 --- a/src/app/Caches/Create/AdvancedTuning/BackupsSiteConfigurator.tsx +++ b/src/app/Caches/Create/AdvancedTuning/BackupsSiteConfigurator.tsx @@ -34,9 +34,7 @@ const BackupSiteConfigurator = (props: { props.backupSiteOptions[props.index]?.failurePolicyClass ); - const [afterFailures, setAfterFailures] = useState( - props.backupSiteOptions![props.index]?.takeOffline?.afterFailures - ); + const [afterFailures, setAfterFailures] = useState(props.backupSiteOptions![props.index]?.takeOffline?.afterFailures); const [minWait, setMinWait] = useState(props.backupSiteOptions[props.index]?.takeOffline?.minWait); const [minWaitUnit, setMinWaitUnit] = useState(props.backupSiteOptions[props.index]?.takeOffline?.minWaitUnit); @@ -126,7 +124,7 @@ const BackupSiteConfigurator = (props: { value={afterFailures} onChange={(e, val) => { const value = parseInt(val); - setAfterFailures(isNaN(value)? undefined!: value) + setAfterFailures(isNaN(value) ? undefined! : value); }} /> @@ -141,12 +139,14 @@ const BackupSiteConfigurator = (props: { /> } > - + ); @@ -211,7 +211,7 @@ const BackupSiteConfigurator = (props: { value={chunckSize} onChange={(e, val) => { const value = parseInt(val); - setChunckSize(isNaN(value)? undefined!: value) + setChunckSize(isNaN(value) ? undefined! : value); }} /> @@ -228,12 +228,14 @@ const BackupSiteConfigurator = (props: { /> } > - + @@ -255,7 +257,7 @@ const BackupSiteConfigurator = (props: { value={maxRetries} onChange={(e, val) => { const value = parseInt(val); - setMaxRetries(isNaN(value)? undefined!: value) + setMaxRetries(isNaN(value) ? undefined! : value); }} /> @@ -272,12 +274,14 @@ const BackupSiteConfigurator = (props: { /> } > - + @@ -342,12 +346,14 @@ const BackupSiteConfigurator = (props: { /> } > - + - setFailurePolicyClass(val === ''? undefined!: val) - } + onChange={(e, val) => setFailurePolicyClass(val === '' ? undefined! : val)} /> {formTakeOffline()} diff --git a/src/app/Caches/Create/AdvancedTuning/IndexedConfigurationTuning.tsx b/src/app/Caches/Create/AdvancedTuning/IndexedConfigurationTuning.tsx index e5e841fc..66e1bb40 100644 --- a/src/app/Caches/Create/AdvancedTuning/IndexedConfigurationTuning.tsx +++ b/src/app/Caches/Create/AdvancedTuning/IndexedConfigurationTuning.tsx @@ -24,11 +24,15 @@ const IndexedConfigurationTuning = () => { //Index Reader const [refreshInterval, setRefreshInterval] = useState(configuration.advanced.indexReader.refreshInterval!); - const [refreshIntervalUnit, setRefreshIntervalUnit] = useState(configuration.advanced.indexReader.refreshIntervalUnit!); + const [refreshIntervalUnit, setRefreshIntervalUnit] = useState( + configuration.advanced.indexReader.refreshIntervalUnit! + ); //Index Writer const [commitInterval, setCommitInterval] = useState(configuration.advanced.indexWriter.commitInterval!); - const [commitIntervalUnit, setCommitIntervalUnit] = useState(configuration.advanced.indexWriter.commitIntervalUnit!); + const [commitIntervalUnit, setCommitIntervalUnit] = useState( + configuration.advanced.indexWriter.commitIntervalUnit! + ); const [lowLevelTrace, setLowLevelTrace] = useState(configuration.advanced.indexWriter.lowLevelTrace!); const [maxBufferedEntries, setMaxBufferedEntries] = useState( configuration.advanced.indexWriter.maxBufferedEntries! @@ -127,12 +131,14 @@ const IndexedConfigurationTuning = () => { /> } > - + ); @@ -180,12 +186,14 @@ const IndexedConfigurationTuning = () => { /> } > - + { placeholder="32" value={ramBufferSize} type="number" - onChange={(_event, val) => - setRamBufferSize(isNaN(parseInt(val)) ?undefined! : parseInt(val)) - } + onChange={(_event, val) => setRamBufferSize(isNaN(parseInt(val)) ? undefined! : parseInt(val))} aria-label="ram-buffer-size" /> diff --git a/src/app/Caches/Create/AdvancedTuning/TransactionalConfigurationTuning.tsx b/src/app/Caches/Create/AdvancedTuning/TransactionalConfigurationTuning.tsx index 332cd847..a569faac 100644 --- a/src/app/Caches/Create/AdvancedTuning/TransactionalConfigurationTuning.tsx +++ b/src/app/Caches/Create/AdvancedTuning/TransactionalConfigurationTuning.tsx @@ -23,9 +23,13 @@ const TransactionalConfigurationTuning = () => { const [stopTimeout, setStopTimeout] = useState(configuration.advanced.transactionalAdvance?.stopTimeout); const [stopTimeoutUnit, setStopTimeoutUnit] = useState(configuration.advanced.transactionalAdvance?.stopTimeoutUnit); const [completeTimeout, setCompleteTimeout] = useState(configuration.advanced.transactionalAdvance?.completeTimeout); - const [completeTimeoutUnit, setCompleteTimeoutUnit] = useState(configuration.advanced.transactionalAdvance?.completeTimeoutUnit); + const [completeTimeoutUnit, setCompleteTimeoutUnit] = useState( + configuration.advanced.transactionalAdvance?.completeTimeoutUnit + ); const [reaperInterval, setReaperInterval] = useState(configuration.advanced.transactionalAdvance?.reaperInterval); - const [reaperIntervalUnit, setReaperIntervalUnit] = useState(configuration.advanced.transactionalAdvance?.reaperIntervalUnit); + const [reaperIntervalUnit, setReaperIntervalUnit] = useState( + configuration.advanced.transactionalAdvance?.reaperIntervalUnit + ); const [isolationLevel, setIsolationLevel] = useState( configuration.advanced.transactionalAdvance?.isolationLevel as IsolationLevel ); @@ -48,7 +52,15 @@ const TransactionalConfigurationTuning = () => { } }; }); - }, [stopTimeout, stopTimeoutUnit, completeTimeout, completeTimeoutUnit, reaperInterval, reaperIntervalUnit, isolationLevel]); + }, [ + stopTimeout, + stopTimeoutUnit, + completeTimeout, + completeTimeoutUnit, + reaperInterval, + reaperIntervalUnit, + isolationLevel + ]); if (!configuration.feature.cacheFeatureSelected.includes(CacheFeature.TRANSACTIONAL)) { return
; @@ -123,12 +135,14 @@ const TransactionalConfigurationTuning = () => { /> } > - + @@ -145,12 +159,14 @@ const TransactionalConfigurationTuning = () => { /> } > - + @@ -167,12 +183,14 @@ const TransactionalConfigurationTuning = () => { /> } > - + diff --git a/src/app/Caches/Create/BasicCacheConfigConfigurator.tsx b/src/app/Caches/Create/BasicCacheConfigConfigurator.tsx index 0dc2f025..4f0b5f98 100644 --- a/src/app/Caches/Create/BasicCacheConfigConfigurator.tsx +++ b/src/app/Caches/Create/BasicCacheConfigConfigurator.tsx @@ -298,13 +298,15 @@ const BasicCacheConfigConfigurator = () => { /> } > - + {validateLifeSpan() === 'error' && ( @@ -328,13 +330,15 @@ const BasicCacheConfigConfigurator = () => { /> } > - + {validateMaxIdle() === 'error' && ( diff --git a/src/app/Caches/Create/Features/PersistentCacheConfigurator.tsx b/src/app/Caches/Create/Features/PersistentCacheConfigurator.tsx index 04425ee8..b13fc682 100644 --- a/src/app/Caches/Create/Features/PersistentCacheConfigurator.tsx +++ b/src/app/Caches/Create/Features/PersistentCacheConfigurator.tsx @@ -3,7 +3,9 @@ import { Alert, Button, FlexItem, - FormGroup, Grid, GridItem, + FormGroup, + Grid, + GridItem, HelperText, HelperTextItem, Hint, @@ -75,7 +77,8 @@ const PersistentCacheConfigurator = () => { } }; }); - }, [passivation, + }, [ + passivation, connectionAttempts, connectionInterval, connectionIntervalUnit, @@ -83,7 +86,8 @@ const PersistentCacheConfigurator = () => { availabilityIntervalUnit, storage, config, - valid]); + valid + ]); const onSelectStorage = (selection) => { setStorage(selection); @@ -172,19 +176,19 @@ const PersistentCacheConfigurator = () => { - setPassivation(!passivation)} - label={t('caches.create.configurations.feature.passivation')} - /> - + setPassivation(!passivation)} + label={t('caches.create.configurations.feature.passivation')} + /> + @@ -230,12 +234,14 @@ const PersistentCacheConfigurator = () => { /> } > - + @@ -253,12 +259,14 @@ const PersistentCacheConfigurator = () => { /> } > - + diff --git a/src/app/Caches/Create/TimeQuantityInputGroup.tsx b/src/app/Caches/Create/TimeQuantityInputGroup.tsx index b29f8e00..2cc1b87c 100644 --- a/src/app/Caches/Create/TimeQuantityInputGroup.tsx +++ b/src/app/Caches/Create/TimeQuantityInputGroup.tsx @@ -4,8 +4,16 @@ import { TimeUnits } from '@services/infinispanRefData'; import { selectOptionProps } from '@utils/selectOptionPropsCreator'; import React from 'react'; -const TimeQuantityInputGroup = (props: { name: string, defaultValue?: string, value: number | undefined; minValue?: number, unit: string | undefined; - valueModifier: (number) => void, unitModifier: (string) => void, validate?: () => 'success' | 'warning' | 'error' | 'default' }) => { +const TimeQuantityInputGroup = (props: { + name: string; + defaultValue?: string; + value: number | undefined; + minValue?: number; + unit: string | undefined; + valueModifier: (number) => void; + unitModifier: (string) => void; + validate?: () => 'success' | 'warning' | 'error' | 'default'; +}) => { return ( @@ -14,13 +22,13 @@ const TimeQuantityInputGroup = (props: { name: string, defaultValue?: string, va { - props.valueModifier(isNaN(parseInt(val))? undefined! : parseInt(val)); + props.valueModifier(isNaN(parseInt(val)) ? undefined! : parseInt(val)); }} aria-label={props.name} /> diff --git a/src/app/Caches/DetailCache.tsx b/src/app/Caches/DetailCache.tsx index a1deab31..ec2b1319 100644 --- a/src/app/Caches/DetailCache.tsx +++ b/src/app/Caches/DetailCache.tsx @@ -63,6 +63,7 @@ import { ThemeContext } from '@app/providers/ThemeProvider'; import { useNavigate } from 'react-router'; import { TracingEnabled } from '@app/Common/TracingEnabled'; import { AlertIcon } from '@patternfly/react-core/dist/js/components/Alert/AlertIcon'; +import { Health } from '@app/Common/Health'; const DetailCache = (props: { cacheName: string }) => { const cacheName = props.cacheName; @@ -84,7 +85,11 @@ const DetailCache = (props: { cacheName: string }) => { return; } - if (cache.editable && ConsoleServices.security().hasCacheConsoleACL(ConsoleACL.READ, cacheName, connectedUser)) { + if ( + cache.started && + cache.editable && + ConsoleServices.security().hasCacheConsoleACL(ConsoleACL.READ, cacheName, connectedUser) + ) { setActiveTabKey1(0); } else if (ConsoleServices.security().hasConsoleACL(ConsoleACL.ADMIN, connectedUser)) { setActiveTabKey1(1); @@ -94,7 +99,7 @@ const DetailCache = (props: { cacheName: string }) => { }, [cache]); const buildEntriesTabContent = () => { - if (!ConsoleServices.security().hasCacheConsoleACL(ConsoleACL.READ, cacheName, connectedUser)) { + if (cache.started && !ConsoleServices.security().hasCacheConsoleACL(ConsoleACL.READ, cacheName, connectedUser)) { return ''; } @@ -127,7 +132,7 @@ const DetailCache = (props: { cacheName: string }) => { }; const entriesTabEnabled = (): boolean => { - return cache.editable && ConsoleServices.security().hasCacheConsoleACL(ConsoleACL.READ, cacheName, connectedUser); + return cache.editable! && ConsoleServices.security().hasCacheConsoleACL(ConsoleACL.READ, cacheName, connectedUser); }; const buildDetailContent = () => { @@ -177,7 +182,11 @@ const DetailCache = (props: { cacheName: string }) => { if (activeTabKey1 == 1) { return ( cache.configuration && ( - + ) ); } @@ -208,11 +217,13 @@ const DetailCache = (props: { cacheName: string }) => { }; const displayBackupsManagement = () => { - return cache?.features.hasRemoteBackup && ConsoleServices.security().hasConsoleACL(ConsoleACL.ADMIN, connectedUser); + return ( + cache?.features?.hasRemoteBackup && ConsoleServices.security().hasConsoleACL(ConsoleACL.ADMIN, connectedUser) + ); }; const displayIndexManage = () => { - return cache?.features.indexed; + return cache?.features?.indexed; }; const buildBackupsManage = () => { @@ -266,7 +277,7 @@ const DetailCache = (props: { cacheName: string }) => { }; const buildTracing = () => { - if (!cacheManager || !cacheManager.tracing_enabled) return; + if (!cacheManager || !cacheManager.tracing_enabled || !cache!.started) return; return ( { {cacheManager.tracing_enabled && ( - + @@ -464,6 +475,36 @@ const DetailCache = (props: { cacheName: string }) => { ); } + if (!cache.started) { + // cache is not ok + return ( + + + + + + + + + {cache.name} + + + + + + + + + {displayActions} + + + + {displayConfiguration()} + + + ); + } + return ( @@ -475,7 +516,7 @@ const DetailCache = (props: { cacheName: string }) => { - + {buildFeaturesChip()} diff --git a/src/app/Caches/Entries/CacheEntries.tsx b/src/app/Caches/Entries/CacheEntries.tsx index 36e78c46..60050118 100644 --- a/src/app/Caches/Entries/CacheEntries.tsx +++ b/src/app/Caches/Entries/CacheEntries.tsx @@ -81,23 +81,23 @@ const CacheEntries = () => { const { syntaxHighLighterTheme } = useContext(ThemeContext); useEffect(() => { - if (cache.encoding.key == EncodingType.Protobuf) { + if (cache.encoding?.key == EncodingType.Protobuf) { setSelectSearchOption(ContentType.string); setKeyContentTypeToEdit(ContentType.string); } else if ( - cache.encoding.key == EncodingType.Java || - cache.encoding.key == EncodingType.JBoss || - cache.encoding.key == EncodingType.JavaSerialized + cache.encoding?.key == EncodingType.Java || + cache.encoding?.key == EncodingType.JBoss || + cache.encoding?.key == EncodingType.JavaSerialized ) { setSelectSearchOption(ContentType.StringContentType); setKeyContentTypeToEdit(ContentType.StringContentType); - } else if (cache.encoding.key == EncodingType.XML) { + } else if (cache.encoding?.key == EncodingType.XML) { setSelectSearchOption(ContentType.XML); setKeyContentTypeToEdit(ContentType.XML); - } else if (cache.encoding.key == EncodingType.JSON) { + } else if (cache.encoding?.key == EncodingType.JSON) { setSelectSearchOption(ContentType.JSON); setSelectSearchOption(ContentType.JSON); - } else if (cache.encoding.key == EncodingType.Text) { + } else if (cache.encoding?.key == EncodingType.Text) { setSelectSearchOption(ContentType.StringContentType); setSelectSearchOption(ContentType.StringContentType); } @@ -284,7 +284,7 @@ const CacheEntries = () => { icon={} headingLevel="h4" /> - {infoEntries ? infoEntries : t('caches.entries.empty-cache-body')} + {infoEntries ? t(infoEntries) : t('caches.entries.empty-cache-body')} {addEntryAction()} @@ -310,7 +310,7 @@ const CacheEntries = () => { }; const keyContentTypeOptions = (): SelectOptionProps[] => { - return selectOptionPropsFromArray(CacheConfigUtils.getContentTypeOptions(cache.encoding.key as EncodingType)); + return selectOptionPropsFromArray(CacheConfigUtils.getContentTypeOptions(cache.encoding?.key as EncodingType)); }; const searchEntryByKey = () => { @@ -398,8 +398,8 @@ const CacheEntries = () => { if (!ConsoleServices.security().hasCacheConsoleACL(ConsoleACL.READ, cache.name, connectedUser)) { return ''; } - const encodingKey = CacheConfigUtils.toEncoding(cache.encoding.key); - const encodingValue = CacheConfigUtils.toEncoding(cache.encoding.value); + const encodingKey = CacheConfigUtils.toEncoding(cache.encoding?.key); + const encodingValue = CacheConfigUtils.toEncoding(cache.encoding?.value); if ( encodingKey == EncodingType.Java || encodingKey == EncodingType.JavaSerialized || @@ -435,6 +435,10 @@ const CacheEntries = () => { return ''; }; + if (!cache.started) { + // Don't display anything if the cache is not started + return <>; + } return ( {encodingMessageDisplay()} @@ -511,14 +515,14 @@ const CacheEntries = () => { {displayHighlighted( row.key, - cache.encoding.key as EncodingType, + cache.encoding?.key as EncodingType, row.keyContentType as ContentType )} {displayHighlighted( row.value, - cache.encoding.value as EncodingType, + cache.encoding?.value as EncodingType, row.valueContentType as ContentType )} @@ -539,7 +543,7 @@ const CacheEntries = () => { )} { /> { - const health = ComponentHealth[props.health]; +const Health = (props: { health?: string; displayIcon?: boolean; cacheName?: string }) => { + const health = props.health ? ComponentHealth[props.health] : ComponentHealth.UNKNOWN; const displayIcon = props.displayIcon == undefined ? true : props.displayIcon; const { theme } = useContext(ThemeContext); diff --git a/src/app/assets/languages/en.json b/src/app/assets/languages/en.json index d42dfc9c..9aada459 100644 --- a/src/app/assets/languages/en.json +++ b/src/app/assets/languages/en.json @@ -11,6 +11,7 @@ "security-realms-docs-link": "https://infinispan.org/docs/stable/titles/security/security.html#security-realms" }, "common": { + "loading": "Loading...", "actions": { "actions": "Actions", "refresh": "Refresh", @@ -575,6 +576,9 @@ "back": "Back" }, "entries": { + "read-error": "Connected user lacks BULK_READ permission to browse the cache content.", + "read-error-unknown-type": "This cache contains entries that can not be read or edited from the Console.", + "read-error-spring-session": "This cache contains Spring Session entries that can not be read or edited from the Console.", "action-edit": "Edit", "action-delete": "Delete", "action-enter": "Enter", diff --git a/src/app/providers/CacheDetailProvider.tsx b/src/app/providers/CacheDetailProvider.tsx index cb8feb01..df63cdc0 100644 --- a/src/app/providers/CacheDetailProvider.tsx +++ b/src/app/providers/CacheDetailProvider.tsx @@ -2,8 +2,9 @@ import React, { useCallback, useEffect, useState } from 'react'; import { ConsoleServices } from '@services/ConsoleServices'; import { useConnectedUser } from '@app/services/userManagementHook'; import { ConsoleACL } from '@services/securityService'; -import { ContentType } from '@services/infinispanRefData'; +import { CacheType, ComponentHealth, ContentType, EncodingType } from '@services/infinispanRefData'; import { isEncodingAvailable } from '@app/utils/encodingUtils'; +import { Health } from '@app/Common/Health'; const initialContext = { error: '', @@ -70,23 +71,63 @@ const CacheDetailProvider = ({ children }) => { if (eitherDetail.isRight()) { setCache(eitherDetail.value); } else { - setError(eitherDetail.value.message); + // Cache can be unhealthy but existing + ConsoleServices.caches() + .retrieveHealth(cacheName) + .then((eitherHealth) => { + if (eitherHealth.isRight()) { + // We have the health. Get the config + return ConsoleServices.caches() + .retrieveConfig(cacheName) + .then((eitherConfig) => { + if (eitherConfig.isRight()) { + const detail: DetailedInfinispanCache = { + name: cacheName, + configuration: eitherConfig.value, + health: eitherHealth.value, + started: false + }; + setCache(detail); + // we are good; + return ''; + } else { + // return the error + return eitherConfig.value.message; + } + }) + .finally(() => { + // loading is over here + setLoading(false); + }); + // we are good + return ''; + } else { + // return the error + return eitherHealth.value.message; + } + }) + .then((error) => { + if (error.length > 0) { + setError(error); + setLoading(false); + } + }); } }) .finally(() => { - setLoading(false); - isEncodingAvailable(cache) && setLoadingEntries(true); + setLoadingEntries(isEncodingAvailable(cache)); }); } else { setError(maybeCm.value.message); } - }); + }) + .finally(() => setLoading(false)); } }; const fetchEntry = (keyToSearch: string, kct: ContentType) => { ConsoleServices.caches() - .getEntry(cacheName, cache.encoding, keyToSearch, kct) + .getEntry(cacheName, cache.encoding!, keyToSearch, kct) .then((response) => { let entries: CacheEntry[] = []; if (response.isRight()) { @@ -103,7 +144,7 @@ const CacheDetailProvider = ({ children }) => { if (ConsoleServices.security().hasCacheConsoleACL(ConsoleACL.BULK_READ, cacheName, connectedUser)) { if (cache) { ConsoleServices.caches() - .getEntries(cacheName, cache.encoding, limit) + .getEntries(cacheName, cache.encoding!, limit) .then((eitherEntries) => { if (eitherEntries.isRight()) { setCacheEntries(eitherEntries.value); @@ -124,7 +165,7 @@ const CacheDetailProvider = ({ children }) => { } } else { setLoadingEntries(false); - setInfoEntries('Connected user lacks BULK_READ permission to browse the cache content.'); + setInfoEntries('caches.entries.read-error'); } } }; diff --git a/src/app/utils/convertToTimeQuantity.ts b/src/app/utils/convertToTimeQuantity.ts index 620f9e10..8261903b 100644 --- a/src/app/utils/convertToTimeQuantity.ts +++ b/src/app/utils/convertToTimeQuantity.ts @@ -5,19 +5,30 @@ import { TimeUnits } from '@services/infinispanRefData'; * @param time * @param format */ -export function convertToTimeQuantity(time?: number, format?: string) : string | undefined { +export function convertToTimeQuantity(time?: number, format?: string): string | undefined { if (!time) { return undefined; } let label = ''; switch (format) { - case TimeUnits.milliseconds: label = 'ms'; break - case TimeUnits.seconds: label = 's'; break; - case TimeUnits.minutes: label = 'm'; break; - case TimeUnits.hours: label = 'h';break; - case TimeUnits.days: label = 'd';break; - default: label = 'ms'; + case TimeUnits.milliseconds: + label = 'ms'; + break; + case TimeUnits.seconds: + label = 's'; + break; + case TimeUnits.minutes: + label = 'm'; + break; + case TimeUnits.hours: + label = 'h'; + break; + case TimeUnits.days: + label = 'd'; + break; + default: + label = 'ms'; } return time + label; } diff --git a/src/app/utils/encodingUtils.ts b/src/app/utils/encodingUtils.ts index 023fa810..f6290c76 100644 --- a/src/app/utils/encodingUtils.ts +++ b/src/app/utils/encodingUtils.ts @@ -1,5 +1,8 @@ import { EncodingType } from '@services/infinispanRefData'; export function isEncodingAvailable(cache: DetailedInfinispanCache): boolean { - return cache?.encoding?.key !== EncodingType.Empty || cache?.encoding?.value !== EncodingType.Empty; + return ( + cache?.encoding !== undefined && + (cache?.encoding?.key !== EncodingType.Empty || cache?.encoding?.value !== EncodingType.Empty) + ); } diff --git a/src/services/cacheConfigUtils.ts b/src/services/cacheConfigUtils.ts index 487dc1ff..fa8d9a2a 100644 --- a/src/services/cacheConfigUtils.ts +++ b/src/services/cacheConfigUtils.ts @@ -246,7 +246,10 @@ export class CacheConfigUtils { cache[cacheType].locking = { striping: data.advanced.striping, 'concurrency-level': data.advanced.concurrencyLevel, - 'acquire-timeout': convertToTimeQuantity(data.advanced.lockAcquisitionTimeout, data.advanced.lockAcquisitionTimeoutUnit) + 'acquire-timeout': convertToTimeQuantity( + data.advanced.lockAcquisitionTimeout, + data.advanced.lockAcquisitionTimeoutUnit + ) }; }; @@ -254,9 +257,9 @@ export class CacheConfigUtils { const expiration = () => { const expiration = 'expiration'; cache[cacheType][expiration] = { - lifespan : convertToTimeQuantity(data.basic.lifeSpanNumber, data.basic.lifeSpanUnit), + lifespan: convertToTimeQuantity(data.basic.lifeSpanNumber, data.basic.lifeSpanUnit), 'max-idle': convertToTimeQuantity(data.basic.maxIdleNumber, data.basic.maxIdleUnit) - } + }; }; // config for Memory and Bounded cache @@ -298,13 +301,19 @@ export class CacheConfigUtils { const indexReader = () => { cache[cacheType]['indexing']['index-reader'] = { - 'refresh-interval': convertToTimeQuantity(data.advanced.indexReader.refreshInterval, data.advanced.indexReader.refreshIntervalUnit) + 'refresh-interval': convertToTimeQuantity( + data.advanced.indexReader.refreshInterval, + data.advanced.indexReader.refreshIntervalUnit + ) }; }; const indexWriter = () => { cache[cacheType]['indexing']['index-writer'] = { - 'commit-interval': convertToTimeQuantity(data.advanced.indexWriter.commitInterval, data.advanced.indexWriter.commitIntervalUnit), + 'commit-interval': convertToTimeQuantity( + data.advanced.indexWriter.commitInterval, + data.advanced.indexWriter.commitIntervalUnit + ), 'max-buffered-entries': data.advanced.indexWriter.maxBufferedEntries, 'queue-count': data.advanced.indexWriter.queueCount, 'queue-size': data.advanced.indexWriter.queueSize, @@ -349,7 +358,10 @@ export class CacheConfigUtils { cache[cacheType]['backups'] = { 'merge-policy': data.advanced.backupSetting?.mergePolicy, 'tombstone-map-size': data.advanced.backupSetting?.tombstoneMapSize, - 'max-cleanup-delay': convertToTimeQuantity(data.advanced.backupSetting?.maxCleanupDelay, data.advanced.backupSetting?.maxCleanupDelayUnit), + 'max-cleanup-delay': convertToTimeQuantity( + data.advanced.backupSetting?.maxCleanupDelay, + data.advanced.backupSetting?.maxCleanupDelayUnit + ) }; }; @@ -359,7 +371,10 @@ export class CacheConfigUtils { backup: { strategy: site.siteStrategy, 'failure-policy': data.advanced.backupSiteData![index].failurePolicy, - timeout: convertToTimeQuantity(data.advanced.backupSiteData![index].timeout,data.advanced.backupSiteData![index].timeoutUnit), + timeout: convertToTimeQuantity( + data.advanced.backupSiteData![index].timeout, + data.advanced.backupSiteData![index].timeoutUnit + ), 'two-phase-commit': data.advanced.backupSiteData![index].twoPhaseCommit, 'failure-policy-class': data.advanced.backupSiteData![index].failurePolicyClass } @@ -370,7 +385,10 @@ export class CacheConfigUtils { ) { cache[cacheType]['backups'][site.siteName!].backup['take-offline'] = { 'after-failures': data.advanced.backupSiteData![index].takeOffline?.afterFailures, - 'min-wait': convertToTimeQuantity(data.advanced.backupSiteData![index].takeOffline?.minWait, data.advanced.backupSiteData![index].takeOffline?.minWaitUnit) + 'min-wait': convertToTimeQuantity( + data.advanced.backupSiteData![index].takeOffline?.minWait, + data.advanced.backupSiteData![index].takeOffline?.minWaitUnit + ) }; } if ( @@ -383,9 +401,15 @@ export class CacheConfigUtils { cache[cacheType]['backups'][site.siteName!].backup['state-transfer'] = { 'chunk-size': data.advanced.backupSiteData![index].stateTransfer?.chunckSize, 'max-retries': data.advanced.backupSiteData![index].stateTransfer?.maxRetries, - timeout: convertToTimeQuantity(data.advanced.backupSiteData![index].stateTransfer?.timeout, data.advanced.backupSiteData![index].stateTransfer?.timeoutUnit), + timeout: convertToTimeQuantity( + data.advanced.backupSiteData![index].stateTransfer?.timeout, + data.advanced.backupSiteData![index].stateTransfer?.timeoutUnit + ), mode: data.advanced.backupSiteData![index].stateTransfer?.mode, - 'wait-time': convertToTimeQuantity(data.advanced.backupSiteData![index].stateTransfer?.waitTime, data.advanced.backupSiteData![index].stateTransfer?.waitTimeUnit) + 'wait-time': convertToTimeQuantity( + data.advanced.backupSiteData![index].stateTransfer?.waitTime, + data.advanced.backupSiteData![index].stateTransfer?.waitTimeUnit + ) }; } }); @@ -402,9 +426,18 @@ export class CacheConfigUtils { cache[cacheType]['transaction'] = { mode: data.feature.transactionalCache.mode, locking: data.feature.transactionalCache.locking, - 'stop-timeout': convertToTimeQuantity(data.advanced.transactionalAdvance?.stopTimeout, data.advanced.transactionalAdvance?.stopTimeoutUnit), - 'complete-timeout': convertToTimeQuantity(data.advanced.transactionalAdvance?.completeTimeout, data.advanced.transactionalAdvance?.completeTimeoutUnit), - 'reaper-interval': convertToTimeQuantity(data.advanced.transactionalAdvance?.reaperInterval, data.advanced.transactionalAdvance?.reaperIntervalUnit), + 'stop-timeout': convertToTimeQuantity( + data.advanced.transactionalAdvance?.stopTimeout, + data.advanced.transactionalAdvance?.stopTimeoutUnit + ), + 'complete-timeout': convertToTimeQuantity( + data.advanced.transactionalAdvance?.completeTimeout, + data.advanced.transactionalAdvance?.completeTimeoutUnit + ), + 'reaper-interval': convertToTimeQuantity( + data.advanced.transactionalAdvance?.reaperInterval, + data.advanced.transactionalAdvance?.reaperIntervalUnit + ) }; }; @@ -420,10 +453,16 @@ export class CacheConfigUtils { cache[cacheType]['persistence']['connection-attempts'] = data.feature.persistentCache.connectionAttempts; } if (data.feature.persistentCache.connectionInterval) { - cache[cacheType]['persistence']['connection-interval'] = convertToTimeQuantity(data.feature.persistentCache.connectionInterval, data.feature.persistentCache.connectionIntervalUnit ); + cache[cacheType]['persistence']['connection-interval'] = convertToTimeQuantity( + data.feature.persistentCache.connectionInterval, + data.feature.persistentCache.connectionIntervalUnit + ); } if (data.feature.persistentCache.availabilityInterval) { - cache[cacheType]['persistence']['availability-interval'] = convertToTimeQuantity(data.feature.persistentCache.availabilityInterval, data.feature.persistentCache.availabilityIntervalUnit ); + cache[cacheType]['persistence']['availability-interval'] = convertToTimeQuantity( + data.feature.persistentCache.availabilityInterval, + data.feature.persistentCache.availabilityIntervalUnit + ); } }; diff --git a/src/services/cacheService.ts b/src/services/cacheService.ts index e04c9113..53e243a0 100644 --- a/src/services/cacheService.ts +++ b/src/services/cacheService.ts @@ -19,6 +19,36 @@ export class CacheService { this.fetchCaller = fetchCaller; } + /** + * Retrieve cache health + * + * @param cacheName + */ + public retrieveHealth(cacheName: string): Promise> { + return this.fetchCaller.get( + this.endpoint + '/caches/' + encodeURIComponent(cacheName) + '?action=health', + (data) => data, + undefined, + true + ); + } + + /** + * Retrieve cache config + * + * @param cacheName + */ + public retrieveConfig(cacheName: string): Promise> { + return this.fetchCaller.get( + this.endpoint + '/caches/' + encodeURIComponent(cacheName) + '?action=config', + (data) => + { + name: cacheName, + config: JSON.stringify(data, null, 2) + } + ); + } + /** * Retrieves all the properties to be displayed in the cache detail in a single rest call * diff --git a/src/services/fetchCaller.ts b/src/services/fetchCaller.ts index 9f993ddd..c4173716 100644 --- a/src/services/fetchCaller.ts +++ b/src/services/fetchCaller.ts @@ -194,10 +194,10 @@ export class FetchCaller { if (text.includes("missing type id property '_type'")) { message = "You are trying to write a JSON key or value that needs '_type' field in this cache."; } else if (text.includes('Unknown type id : 5901')) { - message = 'This cache contains Spring Session entries that can not be read or edited from the Console.'; + message = 'caches.entries.read-error-spring-session'; success = true; } else if (text.includes('Unknown type id')) { - message = 'This cache contains entries that can not be read or edited from the Console.'; + message = 'caches.entries.read-error-unknown-type'; success = true; } else if (text != '') { message = errorMessage + '\n' + text; diff --git a/src/services/infinispanRefData.ts b/src/services/infinispanRefData.ts index ad8b1dce..1fb7de87 100644 --- a/src/services/infinispanRefData.ts +++ b/src/services/infinispanRefData.ts @@ -13,7 +13,8 @@ export enum ComponentHealth { HEALTHY = 'HEALTHY', HEALTHY_REBALANCING = 'HEALTHY_REBALANCING', DEGRADED = 'DEGRADED', - FAILED = 'FAILED' + FAILED = 'FAILED', + UNKNOWN = 'UNKNOWN' } /** * Cache configuration utils class diff --git a/src/types/InfinispanTypes.ts b/src/types/InfinispanTypes.ts index ca999edf..adc305a9 100644 --- a/src/types/InfinispanTypes.ts +++ b/src/types/InfinispanTypes.ts @@ -121,24 +121,25 @@ interface CacheEncoding { interface DetailedInfinispanCache { name: string; configuration?: CacheConfig; - encoding: CacheEncoding; - type: string; + encoding?: CacheEncoding; + type?: string; started: boolean; + health?: string; size?: number; rehash_in_progress?: boolean; indexing_in_progress?: boolean; rebalancing_enabled?: boolean; - editable: boolean; - updateEntry: boolean; - deleteEntry: boolean; - queryable: boolean; - features: Features; + editable?: boolean; + updateEntry?: boolean; + deleteEntry?: boolean; + queryable?: boolean; + features?: Features; backupSites?: [XSite]; stats?: CacheStats; - mode: string; + mode?: string; memory?: CacheMemory; - async: boolean; - tracing: boolean; + async?: boolean; + tracing?: boolean; } interface CacheMemory { @@ -337,8 +338,8 @@ interface BoundedCache { } interface IndexReader { - refreshInterval?:number; - refreshIntervalUnit?:string; + refreshInterval?: number; + refreshIntervalUnit?: string; } interface IndexWriter {