diff --git a/src/api/reloadConfiguration.js b/src/api/reloadConfiguration.js index 51b5cd011..f238be69b 100644 --- a/src/api/reloadConfiguration.js +++ b/src/api/reloadConfiguration.js @@ -1,5 +1,6 @@ import { makeRequest, METHODS } from '@entando/apimanager'; -import { SUCCESS } from 'test/mocks/reloadConfiguration'; +// eslint-disable-next-line import/no-unresolved, import/extensions +import { SUCCESS, STATUS_RESPONSE } from 'test/mocks/reloadConfiguration'; export const reloadConf = () => ( makeRequest({ @@ -11,5 +12,13 @@ export const reloadConf = () => ( }) ); +export const getReloadStatus = () => ( + makeRequest({ + uri: '/api/reloadConfiguration/status', + method: METHODS.GET, + mockResponse: STATUS_RESPONSE, + useAuthentication: true, + }) +); export default reloadConf; diff --git a/src/locales/en.js b/src/locales/en.js index 1b28feb5a..13c5f1c00 100644 --- a/src/locales/en.js +++ b/src/locales/en.js @@ -732,8 +732,16 @@ export default { 'reloadConfiguration.help': 'The RELOAD CONFIGURATION section allows users to reload the system configuration. This operation is necessary after modifying some parameters.', 'reloadConfiguration.reload.title': 'Reload the configuration', 'reloadConfiguration.reload.confirm': 'Are you sure you want to reload the configuration?', - 'reloadConfiguration.confirm.success': 'The configuration has been reloaded.', + 'reloadConfiguration.confirm.success': 'The configuration has been reloaded successfully.', 'reloadConfiguration.confirm.error': 'Something went wrong while reloading the configuration. Try again in a minute.', + 'reloadConfiguration.confirm.progress': 'Reloading configuration in progress...', + 'reloadConfiguration.confirm.pleaseWait': 'Please wait while the system configuration is being reloaded. This page will update automatically.', + 'reloadConfiguration.confirm.waiting': 'Configuration reload completed with warnings. Some components could not be reloaded properly.', + 'reloadConfiguration.confirm.fail': 'Configuration reload failed. Please check the system logs for more details.', + 'reloadConfiguration.table.beanId': 'Component', + 'reloadConfiguration.table.status': 'Status', + 'reloadConfiguration.bean.status.ok': 'OK', + 'reloadConfiguration.bean.status.ko': 'Error', 'activityStream.newPage': 'created a new page', 'activityStream.editPage': 'edited a new page', 'activityStream.deletePage': 'delete a page', diff --git a/src/locales/it.js b/src/locales/it.js index 3e0ddd6b6..57fb5f719 100644 --- a/src/locales/it.js +++ b/src/locales/it.js @@ -732,8 +732,16 @@ export default { 'reloadConfiguration.help': 'Dalla sezione RICARICA CONFIGURAZIONE è possibile ricaricare la configurazione del sistema. Questa operazione si rende necessaria dopo la modifica di alcuni parametri.', 'reloadConfiguration.reload.title': 'Ricarica la Configurazione', 'reloadConfiguration.reload.confirm': 'Sei sicuro di voler ricaricare la configurazione?', - 'reloadConfiguration.confirm.success': 'La configurazione di sistema è stata ricaricata.', + 'reloadConfiguration.confirm.success': 'La configurazione di sistema è stata ricaricata con successo.', 'reloadConfiguration.confirm.error': 'Non è stato possibile ricaricare la configurazione di sistema', + 'reloadConfiguration.confirm.progress': 'Ricaricamento della configurazione in corso...', + 'reloadConfiguration.confirm.pleaseWait': 'Attendere mentre la configurazione del sistema viene ricaricata. Questa pagina si aggiornerà automaticamente.', + 'reloadConfiguration.confirm.waiting': 'Ricaricamento della configurazione completato con avvisi. Alcuni componenti non sono stati ricaricati correttamente.', + 'reloadConfiguration.confirm.fail': 'Ricaricamento della configurazione non riuscito. Controllare i log di sistema per ulteriori dettagli.', + 'reloadConfiguration.table.beanId': 'Componente', + 'reloadConfiguration.table.status': 'Stato', + 'reloadConfiguration.bean.status.ok': 'OK', + 'reloadConfiguration.bean.status.ko': 'Errore', 'activityStream.newPage': 'ha creato una nuova Pagina', 'activityStream.editPage': 'ha modificato una Pagina', 'activityStream.deletePage': 'ha eliminato una Pagina', diff --git a/src/state/reload-configuration/actions.js b/src/state/reload-configuration/actions.js index 25858af82..842bf5c2f 100644 --- a/src/state/reload-configuration/actions.js +++ b/src/state/reload-configuration/actions.js @@ -1,9 +1,11 @@ import { addToast, addErrors, TOAST_ERROR } from '@entando/messages'; -import { reloadConf } from 'api/reloadConfiguration'; -import { SET_STATUS } from 'state/reload-configuration/types'; +import { reloadConf, getReloadStatus } from 'api/reloadConfiguration'; +import { SET_STATUS, SET_RELOAD_INFO, SET_LOADING } from 'state/reload-configuration/types'; import { history, ROUTE_RELOAD_CONFIRM } from 'app-init/router'; +const POLLING_INTERVAL = 500; // Poll every 2 seconds + export const setStatus = status => ({ type: SET_STATUS, payload: { @@ -11,19 +13,83 @@ export const setStatus = status => ({ }, }); +export const setReloadInfo = (percentage, info) => ({ + type: SET_RELOAD_INFO, + payload: { + percentage, + info, + }, +}); + +export const setLoading = loading => ({ + type: SET_LOADING, + payload: { + loading, + }, +}); + +// Poll the reload status +const pollReloadStatus = (dispatch) => { + const poll = () => { + getReloadStatus().then((response) => { + response.json().then((data) => { + if (response.ok) { + const { status, percentage, info } = data.payload; + dispatch(setStatus(status)); + dispatch(setReloadInfo(percentage, info)); + + // Continue polling if still in progress + if (status === 'progress') { + setTimeout(poll, POLLING_INTERVAL); + } else { + // Reload complete - stop loading + dispatch(setLoading(false)); + } + } else { + dispatch(addErrors(data.errors.map(err => err.message))); + dispatch(setLoading(false)); + } + }); + }).catch(() => { + dispatch(setLoading(false)); + }); + }; + + // Start polling + poll(); +}; + // thunk export const sendReloadConf = () => dispatch => new Promise((resolve) => { + // Set initial loading state and status + dispatch(setLoading(true)); + dispatch(setStatus('progress')); + dispatch(setReloadInfo(0, null)); + reloadConf().then((response) => { response.json().then((data) => { if (response.ok) { - dispatch(setStatus(data.payload)); + const { status, percentage, info } = data.payload; + dispatch(setStatus(status)); + dispatch(setReloadInfo(percentage, info)); + + // Navigate to confirm page history.push(ROUTE_RELOAD_CONFIRM); + + // Always start polling to get the latest status + // This ensures we show progress even if reload is very fast + pollReloadStatus(dispatch); + + resolve(); } else { dispatch(addErrors(data.errors.map(err => err.message))); data.errors.forEach(err => dispatch(addToast(err.message, TOAST_ERROR))); + dispatch(setLoading(false)); + resolve(); } - resolve(); }); - }).catch(() => {}); + }).catch(() => { + dispatch(setLoading(false)); + }); }); diff --git a/src/state/reload-configuration/reducer.js b/src/state/reload-configuration/reducer.js index 597dc03bd..52bb7e99c 100644 --- a/src/state/reload-configuration/reducer.js +++ b/src/state/reload-configuration/reducer.js @@ -1,6 +1,14 @@ -import { SET_STATUS } from 'state/reload-configuration/types'; +import { SET_STATUS, SET_RELOAD_INFO, SET_LOADING } from 'state/reload-configuration/types'; +import { combineReducers } from 'redux'; -export const status = (state = {}, action = {}) => { +const initialState = { + status: null, + percentage: null, + info: null, + loading: false, +}; + +export const status = (state = initialState.status, action = {}) => { switch (action.type) { case SET_STATUS: { return action.payload.status; @@ -9,4 +17,36 @@ export const status = (state = {}, action = {}) => { } }; -export default status; +export const percentage = (state = initialState.percentage, action = {}) => { + switch (action.type) { + case SET_RELOAD_INFO: { + return action.payload.percentage; + } + default: return state; + } +}; + +export const info = (state = initialState.info, action = {}) => { + switch (action.type) { + case SET_RELOAD_INFO: { + return action.payload.info; + } + default: return state; + } +}; + +export const loading = (state = initialState.loading, action = {}) => { + switch (action.type) { + case SET_LOADING: { + return action.payload.loading; + } + default: return state; + } +}; + +export default combineReducers({ + status, + percentage, + info, + loading, +}); diff --git a/src/state/reload-configuration/selectors.js b/src/state/reload-configuration/selectors.js index fff7d2110..980ec9958 100644 --- a/src/state/reload-configuration/selectors.js +++ b/src/state/reload-configuration/selectors.js @@ -1,3 +1,6 @@ // eslint-disable-next-line export const getConfiguration = state => state.configuration; export const getStatus = state => state.configuration.status; +export const getPercentage = state => state.configuration.percentage; +export const getInfo = state => state.configuration.info; +export const getLoading = state => state.configuration.loading; diff --git a/src/state/reload-configuration/types.js b/src/state/reload-configuration/types.js index 7c36e3e21..d66c5f3cf 100644 --- a/src/state/reload-configuration/types.js +++ b/src/state/reload-configuration/types.js @@ -1,2 +1,4 @@ // eslint-disable-next-line export const SET_STATUS = 'reload-configuration/set-status'; +export const SET_RELOAD_INFO = 'reload-configuration/set-reload-info'; +export const SET_LOADING = 'reload-configuration/set-loading'; diff --git a/src/ui/reload-configuration/ReloadConfirm.js b/src/ui/reload-configuration/ReloadConfirm.js index b133f2efc..3cbe32cb0 100644 --- a/src/ui/reload-configuration/ReloadConfirm.js +++ b/src/ui/reload-configuration/ReloadConfirm.js @@ -1,26 +1,109 @@ import React from 'react'; import { Alert } from 'patternfly-react'; +import { Table } from 'react-bootstrap'; import PropTypes from 'prop-types'; import { FormattedMessage } from 'react-intl'; -const ReloadConfirm = ({ status }) => { - const alertType = status === 'success' ? 'success' : 'danger'; +const ReloadConfirm = ({ + status, percentage, info, loading, +}) => { + // Determine alert type based on status + let alertType = 'info'; + let messageId = 'reloadConfiguration.confirm.progress'; + + if (status === 'success') { + alertType = 'success'; + messageId = 'reloadConfiguration.confirm.success'; + } else if (status === 'waiting') { + alertType = 'warning'; + messageId = 'reloadConfiguration.confirm.waiting'; + } else if (status === 'fail') { + alertType = 'danger'; + messageId = 'reloadConfiguration.confirm.fail'; + } + + // Render info table if available + const renderInfoTable = () => { + if (!info || Object.keys(info).length === 0) { + return null; + } + + const entries = Object.entries(info); + + return ( +
| # | +
+ |
+
+ |
+
|---|---|---|
| {index + 1} | ++ {hasError ? {beanId} : beanId} + | +
+ {hasError ? (
+
+ |
+
+