{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
)}
@@ -481,26 +542,22 @@ const CacheEntries = (props: { cacheName: string }) => {
)}
-
+
);
};
diff --git a/src/app/Common/Health.tsx b/src/app/Common/Health.tsx
index 75088ba4..2106c197 100644
--- a/src/app/Common/Health.tsx
+++ b/src/app/Common/Health.tsx
@@ -6,8 +6,8 @@ import { ComponentHealth } from '@services/infinispanRefData';
import { ThemeContext } from '@app/providers/ThemeProvider';
import { chart_global_label_Fill, global_Color_light_100 } from '@patternfly/react-tokens';
-const Health = (props: { health: string; displayIcon?: boolean; cacheName?: string }) => {
- 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/Common/SelectMultiWithChips.tsx b/src/app/Common/SelectMultiWithChips.tsx
index b92c739a..20d797fa 100644
--- a/src/app/Common/SelectMultiWithChips.tsx
+++ b/src/app/Common/SelectMultiWithChips.tsx
@@ -49,8 +49,10 @@ const SelectMultiWithChips = (props: {
);
// When no options are found after filtering, display creation option
- if (!newSelectOptions.length) {
- newSelectOptions = [{ isDisabled: false, children: `Create new option "${inputValue}"`, value: 'create' }];
+ if (!newSelectOptions.length && props.create) {
+ newSelectOptions = [{ isDisabled: false, children: `Create "${inputValue}"`, value: 'create' }];
+ } else if (!newSelectOptions.length) {
+ newSelectOptions = [{ isDisabled: false, children: 'no results', value: 'no results' }];
}
// Open the menu when the input value changes and the new value is not empty
@@ -104,6 +106,7 @@ const SelectMultiWithChips = (props: {
setIsOpen((prevIsOpen) => !prevIsOpen);
} else if (isOpen && focusedItem.value !== 'no results') {
onSelect(focusedItem.value as string);
+ setInputValue('');
}
break;
case 'Tab':
diff --git a/src/app/ConnectedClients/ConnectedClients.tsx b/src/app/ConnectedClients/ConnectedClients.tsx
index e577afbd..ce379ee7 100644
--- a/src/app/ConnectedClients/ConnectedClients.tsx
+++ b/src/app/ConnectedClients/ConnectedClients.tsx
@@ -29,7 +29,12 @@ import {
ToolbarItem,
ToolbarItemVariant,
ToolbarGroup,
- EmptyStateHeader, Dropdown, MenuToggleElement, MenuToggle, DropdownList, DropdownItem
+ EmptyStateHeader,
+ Dropdown,
+ MenuToggleElement,
+ MenuToggle,
+ DropdownList,
+ DropdownItem
} from '@patternfly/react-core';
import { CubesIcon, SearchIcon, InfoCircleIcon, RedoIcon } from '@patternfly/react-icons';
import { Table, Thead, Tr, Th, Tbody, Td } from '@patternfly/react-table';
@@ -241,13 +246,17 @@ const ConnectedClients = () => {
{t('connected-clients.client-version')}
-
+
{t('connected-clients.local-address')}
-
+
@@ -289,11 +298,9 @@ const ConnectedClients = () => {
if (connectedClients.length === 0) {
return (
-
- {emptyPage}
-
+ {emptyPage}
- )
+ );
}
return (
@@ -365,8 +372,8 @@ const ConnectedClients = () => {
- )
- }
+ );
+ };
const displayActions = (
@@ -384,7 +391,13 @@ const ConnectedClients = () => {
shouldFocusToggleOnSelect
>
- setLoading(true)} icon={}>
+ setLoading(true)}
+ icon={}
+ >
{t('common.actions.refresh')}
diff --git a/src/app/assets/languages/en.json b/src/app/assets/languages/en.json
index f44b85de..cd8368d9 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",
@@ -554,6 +555,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..5242b6f3 100644
--- a/src/app/providers/CacheDetailProvider.tsx
+++ b/src/app/providers/CacheDetailProvider.tsx
@@ -70,23 +70,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 +143,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 +164,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/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/app/utils/getLanguage.ts b/src/app/utils/getLanguage.ts
index d9e4304c..5f64c23f 100644
--- a/src/app/utils/getLanguage.ts
+++ b/src/app/utils/getLanguage.ts
@@ -2,15 +2,15 @@ import { ConfigDownloadType } from '@services/infinispanRefData';
import { Language } from '@patternfly/react-code-editor';
export const toCodeEditorLanguage = (lang: ConfigDownloadType) => {
- if (ConfigDownloadType.JSON == lang ) {
+ if (ConfigDownloadType.JSON == lang) {
return Language.json;
}
- if (ConfigDownloadType.YAML == lang ) {
+ if (ConfigDownloadType.YAML == lang) {
return Language.yaml;
}
- if (ConfigDownloadType.XML == lang ) {
+ if (ConfigDownloadType.XML == lang) {
return Language.xml;
}
diff --git a/src/services/cacheConfigUtils.ts b/src/services/cacheConfigUtils.ts
index 3df501da..4fa34b9d 100644
--- a/src/services/cacheConfigUtils.ts
+++ b/src/services/cacheConfigUtils.ts
@@ -76,30 +76,6 @@ export class CacheConfigUtils {
return EncodingType.Empty;
}
- /**
- * Map cache name from cache mode
- * @param mode or cache type name
- */
- public static mapCacheTypeFromCacheMode(mode: string): string {
- if (mode.includes('DIST')) {
- return 'Distributed';
- }
-
- if (mode.includes('REPL')) {
- return 'Replicated';
- }
-
- if (mode.includes('LOCAL')) {
- return 'Local';
- }
-
- if (mode.includes('INVALIDATION')) {
- return 'Invalidated';
- }
-
- return 'Unknown';
- }
-
/**
* Map cache name from json configuration or from the label in the conf
* @param config or cache type name
@@ -145,6 +121,34 @@ export class CacheConfigUtils {
);
}
+ /**
+ * Retrieve if an encoding can display entries
+ *
+ * @param encoding
+ */
+ public static canUpdateEntries(encoding: EncodingType): boolean {
+ return (
+ encoding == EncodingType.Text ||
+ encoding == EncodingType.JSON ||
+ encoding == EncodingType.XML ||
+ encoding == EncodingType.Protobuf
+ );
+ }
+
+ /**
+ * Retrieve if an encoding can display entries
+ *
+ * @param encoding
+ */
+ public static canDeleteEntries(encoding: EncodingType): boolean {
+ return (
+ encoding == EncodingType.Text ||
+ encoding == EncodingType.JSON ||
+ encoding == EncodingType.XML ||
+ encoding == EncodingType.Protobuf
+ );
+ }
+
/**
* Map accepted content types to encoding
*
diff --git a/src/services/cacheService.ts b/src/services/cacheService.ts
index 09a18918..8d59c971 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
*
@@ -67,6 +97,8 @@ export class CacheService {
data.indexed ||
(CacheConfigUtils.isEditable(keyValueEncoding.key as EncodingType) &&
CacheConfigUtils.isEditable(keyValueEncoding.value as EncodingType)),
+ updateEntry: CacheConfigUtils.canUpdateEntries(keyValueEncoding.key as EncodingType),
+ deleteEntry: CacheConfigUtils.canDeleteEntries(keyValueEncoding.key as EncodingType),
queryable: data.queryable,
features: {
bounded: data.bounded,
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 d51c7c68..44e1ff02 100644
--- a/src/services/infinispanRefData.ts
+++ b/src/services/infinispanRefData.ts
@@ -11,7 +11,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 cc5949c9..2928a669 100644
--- a/src/types/InfinispanTypes.ts
+++ b/src/types/InfinispanTypes.ts
@@ -119,21 +119,24 @@ 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;
- 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;
+ async?: boolean;
}
interface CacheMemory {