diff --git a/src/app/CacheManagers/CacheManagers.tsx b/src/app/CacheManagers/CacheManagers.tsx
index 970ed3b80..aa56c6778 100644
--- a/src/app/CacheManagers/CacheManagers.tsx
+++ b/src/app/CacheManagers/CacheManagers.tsx
@@ -29,6 +29,7 @@ import {useTranslation} from 'react-i18next';
import {useConnectedUser} from "@app/services/userManagementHook";
import {ConsoleServices} from "@services/ConsoleServices";
import {ConsoleACL} from "@services/securityService";
+import {RebalancingCacheManager} from "@app/CacheManagers/RebalancingCacheManager";
const CacheManagers = () => {
const { connectedUser } = useConnectedUser();
@@ -207,6 +208,7 @@ const CacheManagers = () => {
{buildSiteDisplay(cm.local_site)}
+
{buildTabs()}
diff --git a/src/app/CacheManagers/RebalancingCacheManager.tsx b/src/app/CacheManagers/RebalancingCacheManager.tsx
new file mode 100644
index 000000000..2be5adfaa
--- /dev/null
+++ b/src/app/CacheManagers/RebalancingCacheManager.tsx
@@ -0,0 +1,51 @@
+import React from 'react';
+import {Divider, FlexItem, Spinner, Switch} from '@patternfly/react-core';
+import {useConnectedUser} from "@app/services/userManagementHook";
+import {ConsoleServices} from "@services/ConsoleServices";
+import {ConsoleACL} from "@services/securityService";
+import {useTranslation} from "react-i18next";
+import {useApiAlert} from "@app/utils/useApiAlert";
+import {useDataContainer} from "@app/services/dataContainerHooks";
+
+const RebalancingCacheManager = () => {
+ const { addAlert } = useApiAlert();
+ const { t } = useTranslation();
+ const { connectedUser } = useConnectedUser();
+ const { cm, loading, reload } = useDataContainer();
+
+ if (loading || !cm) {
+ return (
+
+
+
+ );
+ }
+
+ if (ConsoleServices.security().hasConsoleACL(ConsoleACL.ADMIN, connectedUser)) {
+ return (
+
+
+
+ {
+ ConsoleServices.dataContainer().rebalancing(cm.name, !cm.rebalancing_enabled)
+ .then(r => {
+ addAlert(r);
+ reload();
+ })
+ }}
+ />
+
+
+ );
+ }
+
+ // Return nothing if the connected user is not ADMIN
+ return ();
+};
+
+export { RebalancingCacheManager };
diff --git a/src/app/Caches/DetailCache.tsx b/src/app/Caches/DetailCache.tsx
index 6f51223d3..c7458f407 100644
--- a/src/app/Caches/DetailCache.tsx
+++ b/src/app/Caches/DetailCache.tsx
@@ -43,6 +43,7 @@ import {ConsoleServices} from "@services/ConsoleServices";
import {ConsoleACL} from "@services/securityService";
import {useConnectedUser} from "@app/services/userManagementHook";
import {useTranslation} from "react-i18next";
+import {RebalancingCache} from "@app/Caches/RebalancingCache";
const DetailCache = (props: { cacheName: string }) => {
const cacheName = props.cacheName;
@@ -104,17 +105,7 @@ const DetailCache = (props: { cacheName: string }) => {
};
const buildDetailContent = () => {
- if (loading) {
- return (
-
-
-
-
-
- );
- }
-
- if (error.length > 0 || !cache) {
+ if (error.length > 0) {
return (
@@ -142,6 +133,16 @@ const DetailCache = (props: { cacheName: string }) => {
);
}
+ if (loading || !cache) {
+ return (
+
+
+
+
+
+ );
+ }
+
if(activeTabKey1 == 0
&& cache.editable
&& ConsoleServices.security().hasCacheConsoleACL(ConsoleACL.READ, cacheName, connectedUser)) {
@@ -162,6 +163,8 @@ const DetailCache = (props: { cacheName: string }) => {
};
const buildRebalancing = () => {
+ if (!cache) return ;
+
if (!cache?.rehash_in_progress) {
return (
@@ -308,7 +311,7 @@ const DetailCache = (props: { cacheName: string }) => {
- {buildRebalancing()}
+
{buildBackupsManage()}
{buildIndexManage()}
@@ -416,7 +419,12 @@ const DetailCache = (props: { cacheName: string }) => {
activeKey={activeTabKey1}
isSecondary={true}
component={TabsComponent.nav}
- onSelect={(event, tabIndex) => setActiveTabKey1(tabIndex)}
+ onSelect={(event, tabIndex) => {
+ setActiveTabKey1(tabIndex);
+ if (tabIndex == 0) {
+ loadCache(cacheName);
+ }
+ }}
>
{displayCacheEntries()}
{displayConfiguration()}
diff --git a/src/app/Caches/RebalancingCache.tsx b/src/app/Caches/RebalancingCache.tsx
new file mode 100644
index 000000000..29498693f
--- /dev/null
+++ b/src/app/Caches/RebalancingCache.tsx
@@ -0,0 +1,71 @@
+import React from 'react';
+import {Label, Spinner, Switch, ToolbarItem} from '@patternfly/react-core';
+import {useCacheDetail} from "@app/services/cachesHook";
+import {useConnectedUser} from "@app/services/userManagementHook";
+import {ConsoleServices} from "@services/ConsoleServices";
+import {ConsoleACL} from "@services/securityService";
+import {useTranslation} from "react-i18next";
+import {useApiAlert} from "@app/utils/useApiAlert";
+import {useDataContainer} from "@app/services/dataContainerHooks";
+
+const RebalancingCache = () => {
+ const { addAlert } = useApiAlert();
+ const { t } = useTranslation();
+ const { connectedUser } = useConnectedUser();
+ const { cache, cacheManager, loading, reload } = useCacheDetail();
+
+ // If rebalancing is not activated at cluster level, don't display anything
+ if (!cacheManager.rebalancing_enabled) {
+ return ( )
+ }
+
+ if (loading || !cache) {
+ return (
+
+
+
+ );
+ }
+
+ /**
+ * When the rehash is in progress just display the value
+ */
+ if (cache?.rehash_in_progress) {
+ return (
+
+ {t('caches.info.rebalancing')}
+
+ );
+ }
+
+ /**
+ * If the user is ADMIN, can enable and disable rebalancing
+ */
+ if (ConsoleServices.security().hasConsoleACL(ConsoleACL.ADMIN, connectedUser)) {
+ return (
+
+ {
+ ConsoleServices.caches().rebalancing(cache.name, !cache.rebalancing_enabled)
+ .then(r => {
+ addAlert(r);
+ reload();
+ })
+ }}
+ />
+
+ );
+ }
+
+ return (
+
+
+
+ );
+};
+
+export { RebalancingCache };
diff --git a/src/app/assets/languages/en.json b/src/app/assets/languages/en.json
index 3d45e3a4a..d29e14611 100644
--- a/src/app/assets/languages/en.json
+++ b/src/app/assets/languages/en.json
@@ -104,7 +104,9 @@
"allowed-role" : "Allowed role",
"allowed-role-null" : "-",
"no-tasks-status" : "No tasks yet",
- "no-tasks-body" : "Create tasks with the CLI or a remote client."
+ "no-tasks-body" : "Create tasks with the CLI or a remote client.",
+ "rebalancing-enabled": "Rebalancing is on",
+ "rebalancing-disabled": "Rebalancing is off"
},
"caches" : {
"configuration" : {
@@ -129,7 +131,8 @@
"loading" : "Loading cache {{cacheName}} ...",
"error" : "An error occurred while loading {{cacheName}}",
"rebalanced" : "Rebalanced",
- "rebalancing" : "Rebalancing"
+ "rebalancing-enabled" : "Rebalancing is on",
+ "rebalancing-disabled" : "Rebalancing is off"
},
"actions" : {
"action-see-less" : "See fewer cache details",
diff --git a/src/app/providers/CacheDetailProvider.tsx b/src/app/providers/CacheDetailProvider.tsx
index f6960c5bc..a920a82aa 100644
--- a/src/app/providers/CacheDetailProvider.tsx
+++ b/src/app/providers/CacheDetailProvider.tsx
@@ -7,6 +7,7 @@ import {ContentType} from "@services/infinispanRefData";
const initialContext = {
error: '',
loading: false,
+ cacheManager: (undefined as unknown) as CacheManager,
cache: (undefined as unknown) as DetailedInfinispanCache,
loadCache: (name: string) => {},
reload: () => {},
@@ -23,6 +24,9 @@ export const CacheDetailContext = React.createContext(initialContext);
const CacheDetailProvider = ({ children }) => {
const {connectedUser} = useConnectedUser();
const [cacheName, setCacheName] = useState('');
+ const [cacheManager, setCacheManager] = useState(
+ initialContext.cacheManager
+ );
const [cache, setCache] = useState(
initialContext.cache
);
@@ -52,19 +56,28 @@ const CacheDetailProvider = ({ children }) => {
const fetchCache = () => {
if (loading) {
- ConsoleServices.caches()
- .retrieveFullDetail(cacheName)
- .then((eitherDetail) => {
- if (eitherDetail.isRight()) {
- setCache(eitherDetail.value);
- } else {
- setError(eitherDetail.value.message);
- }
- })
- .finally(() => {
- setLoading(false);
- setLoadingEntries(true);
- });
+ ConsoleServices.dataContainer().getDefaultCacheManager()
+ .then(maybeCm => {
+ if (maybeCm.isRight()) {
+ setCacheManager(maybeCm.value);
+ ConsoleServices.caches()
+ .retrieveFullDetail(cacheName)
+ .then((eitherDetail) => {
+ if (eitherDetail.isRight()) {
+ setCache(eitherDetail.value);
+ } else {
+ setError(eitherDetail.value.message);
+ }
+ })
+ .finally(() => {
+ setLoading(false);
+ setLoadingEntries(true);
+ });
+ } else {
+ setError(maybeCm.value.message);
+ }
+ })
+
}
};
@@ -117,6 +130,7 @@ const CacheDetailProvider = ({ children }) => {
loadCache: useCallback(loadCache, []),
reload: useCallback(() => setLoading(true), []),
cache: cache,
+ cacheManager: cacheManager,
cacheEntries: cacheEntries,
loadingEntries: loadingEntries,
errorEntries: errorEntries,
diff --git a/src/app/providers/CacheManagerContextProvider.tsx b/src/app/providers/CacheManagerContextProvider.tsx
index bcbbc8ae4..6b547f764 100644
--- a/src/app/providers/CacheManagerContextProvider.tsx
+++ b/src/app/providers/CacheManagerContextProvider.tsx
@@ -9,6 +9,7 @@ const initialContext = {
caches: [] as CacheInfo[],
loadingCaches: true,
errorCaches: '',
+ reload: () => {},
reloadCaches: () => {},
};
@@ -76,6 +77,10 @@ const ContainerDataProvider = ({ children }) => {
}
}, [cm, loadingCaches]);
+ const reload = () => {
+ setLoading(true);
+ };
+
const reloadCaches = () => {
setLoadingCaches(true);
};
@@ -87,6 +92,7 @@ const ContainerDataProvider = ({ children }) => {
cm: cm,
loadingCaches: loadingCaches,
errorCaches: errorCaches,
+ reload: useCallback(reload, []),
reloadCaches: useCallback(reloadCaches, []),
};
diff --git a/src/app/services/cachesHook.ts b/src/app/services/cachesHook.ts
index 97ab47a14..b0f5a2443 100644
--- a/src/app/services/cachesHook.ts
+++ b/src/app/services/cachesHook.ts
@@ -54,9 +54,9 @@ export function useCacheEntries() {
}
export function useCacheDetail() {
- const { cache, loading, error, loadCache, reload } = useContext(
+ const { cache, loading, error, loadCache, reload, cacheManager } = useContext(
CacheDetailContext
);
- return { cache, loading, error, loadCache, reload };
+ return { cache, loading, error, loadCache, reload, cacheManager };
}
diff --git a/src/app/services/dataContainerHooks.ts b/src/app/services/dataContainerHooks.ts
index 8c1d8d7b8..3c274e647 100644
--- a/src/app/services/dataContainerHooks.ts
+++ b/src/app/services/dataContainerHooks.ts
@@ -2,11 +2,12 @@ import { useContext } from 'react';
import { DataContainerContext } from '@app/providers/CacheManagerContextProvider';
export function useDataContainer() {
- const { cm, loading, error } = useContext(DataContainerContext);
+ const { cm, loading, error, reload } = useContext(DataContainerContext);
return {
loading,
error,
cm,
+ reload
};
}
diff --git a/src/services/cacheService.ts b/src/services/cacheService.ts
index 3c4868eed..ae9adbe19 100644
--- a/src/services/cacheService.ts
+++ b/src/services/cacheService.ts
@@ -68,6 +68,7 @@ export class CacheService {
size: data['size'],
rehash_in_progress: data['rehash_in_progress'],
indexing_in_progress: data['indexing_in_progress'],
+ rebalancing_enabled: data['rebalancing_enabled'],
editable:
CacheConfigUtils.isEditable(keyValueEncoding.key as EncodingType) &&
CacheConfigUtils.isEditable(keyValueEncoding.value as EncodingType),
@@ -552,4 +553,27 @@ export class CacheService {
)
);
}
+
+ /**
+ * Enables or disables rebalancing on a cache
+ * @param cacheName
+ * @param enable, true to enable, false for disable
+ */
+ public async rebalancing(
+ cacheName: string,
+ enable: boolean
+ ): Promise {
+ const action = enable? 'enable' : 'disable';
+ const url = this.endpoint +
+ '/caches/' +
+ encodeURIComponent(cacheName) +
+ '?action=' + action + '-rebalancing';
+ return this.fetchCaller
+ .post( {
+ url: url,
+ successMessage: `Cache ${cacheName} rebalancing successfully ${action}d.`,
+ errorMessage: `Unexpected error when cache ${cacheName} rebalancing ${action}d.`
+ });
+ }
+
}
diff --git a/src/services/dataContainerService.ts b/src/services/dataContainerService.ts
index e25d9bce8..bf6ce95e8 100644
--- a/src/services/dataContainerService.ts
+++ b/src/services/dataContainerService.ts
@@ -5,11 +5,11 @@ import displayUtils from '@services/displayUtils';
export class ContainerService {
endpoint: string;
- utils: FetchCaller;
+ fetchCaller: FetchCaller;
- constructor(endpoint: string, restUtils: FetchCaller) {
+ constructor(endpoint: string, fetchCaller: FetchCaller) {
this.endpoint = endpoint;
- this.utils = restUtils;
+ this.fetchCaller = fetchCaller;
}
/**
@@ -18,7 +18,7 @@ export class ContainerService {
public getDefaultCacheManager(): Promise<
Either
> {
- return this.utils
+ return this.fetchCaller
.get(this.endpoint + '/server/cache-managers/', (data) => data[0])
.then((maybeCmName) => {
if (maybeCmName.isRight()) {
@@ -31,13 +31,13 @@ export class ContainerService {
private getCacheManager(
name: string
): Promise> {
- let healthPromise: Promise> = this.utils.get(
+ let healthPromise: Promise> = this.fetchCaller.get(
this.endpoint + '/cache-managers/' + name + '/health',
(data) => data.cluster_health.health_status
);
return healthPromise.then((maybeHealth) =>
- this.utils.get(
+ this.fetchCaller.get(
this.endpoint + '/cache-managers/' + name,
(data) =>
{
@@ -61,6 +61,7 @@ export class ContainerService {
? maybeHealth.value
: maybeHealth.value.message,
local_site: data.local_site,
+ rebalancing_enabled: data.rebalancing_enabled
}
)
);
@@ -82,7 +83,7 @@ export class ContainerService {
public async getCacheManagerStats(
name: string
): Promise> {
- return this.utils.get(
+ return this.fetchCaller.get(
this.endpoint + '/cache-managers/' + name + '/stats',
(data) => data
);
@@ -96,7 +97,7 @@ export class ContainerService {
public async getCacheConfigurationTemplates(
name: string
): Promise> {
- return this.utils.get(
+ return this.fetchCaller.get(
this.endpoint + '/cache-managers/' + name + '/cache-configs/templates',
(data) =>
data.map(
@@ -116,7 +117,7 @@ export class ContainerService {
public async getCaches(
name: string
): Promise> {
- return this.utils.get(
+ return this.fetchCaller.get(
this.endpoint + '/cache-managers/' + name + '/caches',
(data) =>
data
@@ -142,6 +143,28 @@ export class ContainerService {
);
}
+ /**
+ * Enables or disables rebalancing on a cluster
+ * @param name of the cache manager
+ * @param enable, true to enable, false for disable
+ */
+ public async rebalancing(
+ name: string,
+ enable: boolean
+ ): Promise {
+ const action = enable? 'enable' : 'disable';
+ const url = this.endpoint +
+ '/cache-managers/' +
+ encodeURIComponent(name) +
+ '?action=' + action + '-rebalancing';
+ return this.fetchCaller
+ .post( {
+ url: url,
+ successMessage: `Rebalancing successfully ${action}d.`,
+ errorMessage: `Unexpected error when rebalancing ${action}d.`
+ });
+ }
+
private clusterMembers(
cluster_members: [string],
cluster_members_physical_addresses: [string]
diff --git a/src/types/InfinispanTypes.ts b/src/types/InfinispanTypes.ts
index 2a6d0368e..8b7c5e7b7 100644
--- a/src/types/InfinispanTypes.ts
+++ b/src/types/InfinispanTypes.ts
@@ -16,6 +16,7 @@ interface CacheManager {
cluster_members: [ClusterMember];
health: string;
local_site?: string;
+ rebalancing_enabled:boolean;
}
interface ClusterMember {
@@ -113,6 +114,7 @@ interface DetailedInfinispanCache {
size?: number;
rehash_in_progress?: boolean;
indexing_in_progress?: boolean;
+ rebalancing_enabled: boolean;
editable: boolean;
queryable: boolean;
features: Features;