Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
319 changes: 94 additions & 225 deletions src/components/ClusterStatGrids.jsx

Large diffs are not rendered by default.

49 changes: 36 additions & 13 deletions src/components/ConfigSection.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import DeleteIcon from "@mui/icons-material/Delete";
import {URL_OBJECT, URL_NODE} from "../config/apiPath.js";
import {parseObjectPath} from "../utils/objectUtils";

const useConfig = (decodedObjectName, configNode, setConfigNode) => {
const useConfig = (decodedObjectName, configNode, setConfigNode, refreshTrigger) => {
const initialState = {
data: null,
loading: false,
Expand All @@ -43,20 +43,23 @@ const useConfig = (decodedObjectName, configNode, setConfigNode) => {
return {...state, loading: false, data: action.payload};
case "FETCH_ERROR":
return {...state, loading: false, error: action.payload};
case "RESET":
return initialState;
default:
return state;
}
};
const [state, dispatch] = useReducer(reducer, initialState);
const lastFetch = useRef({});
const fetchConfig = useCallback(async (node) => {

const fetchConfig = useCallback(async (node, forceBypassThrottle = false) => {
if (!node) {
dispatch({type: "FETCH_ERROR", payload: "No node available to fetch configuration."});
dispatch({type: "RESET"});
return;
}
const key = `${decodedObjectName}:${node}`;
const now = Date.now();
if (lastFetch.current[key] && now - lastFetch.current[key] < 1000) return;
if (!forceBypassThrottle && lastFetch.current[key] && now - lastFetch.current[key] < 1000) return;
lastFetch.current[key] = now;
const {namespace, kind, name} = parseObjectPath(decodedObjectName);
const token = localStorage.getItem("authToken") || "";
Expand All @@ -78,13 +81,26 @@ const useConfig = (decodedObjectName, configNode, setConfigNode) => {
dispatch({type: "FETCH_ERROR", payload: `Failed to fetch config: ${err.message}`});
}
}, [decodedObjectName, setConfigNode]);

useEffect(() => {
if (configNode) {
fetchConfig(configNode);
} else {
dispatch({type: "FETCH_ERROR", payload: "No node available to fetch configuration."});
dispatch({type: "RESET"});
}
}, [configNode, decodedObjectName, fetchConfig]);

// Force re-fetch when refreshTrigger bumps (external config change via SSE)
const prevRefreshTrigger = useRef(refreshTrigger);
useEffect(() => {
if (refreshTrigger === prevRefreshTrigger.current) return;
prevRefreshTrigger.current = refreshTrigger;
if (configNode) {
// Bypass throttle so the fresh content is fetched immediately
fetchConfig(configNode, true);
}
}, [refreshTrigger, configNode, fetchConfig]);

return {...state, fetchConfig};
};

Expand Down Expand Up @@ -589,15 +605,17 @@ const ConfigSection = ({
setConfigNode,
openSnackbar,
configDialogOpen,
setConfigDialogOpen
setConfigDialogOpen,
configRefreshTrigger = 0,
}) => {
const theme = useTheme();
const isDark = theme.palette.mode === 'dark';

const {data: configData, loading: configLoading, error: configError, fetchConfig} = useConfig(
decodedObjectName,
configNode,
setConfigNode
setConfigNode,
configRefreshTrigger,
);
const {
data: keywordsData,
Expand Down Expand Up @@ -661,7 +679,7 @@ const ConfigSection = ({
}
openSnackbar("Configuration updated successfully");
if (configNode) {
await fetchConfig(configNode);
await fetchConfig(configNode, true);
setConfigDialogOpen(true);
}
} catch (err) {
Expand Down Expand Up @@ -728,7 +746,7 @@ const ConfigSection = ({
if (successCount > 0) {
openSnackbar(`Successfully added ${successCount} parameter(s)`, "success");
if (configNode) {
await fetchConfig(configNode);
await fetchConfig(configNode, true);
await fetchExistingParams();
setConfigDialogOpen(true);
}
Expand Down Expand Up @@ -773,7 +791,7 @@ const ConfigSection = ({
if (successCount > 0) {
openSnackbar(`Successfully unset ${successCount} parameter(s)`, "success");
if (configNode) {
await fetchConfig(configNode);
await fetchConfig(configNode, true);
await fetchExistingParams();
setConfigDialogOpen(true);
}
Expand Down Expand Up @@ -812,7 +830,7 @@ const ConfigSection = ({
if (successCount > 0) {
openSnackbar(`Successfully deleted ${successCount} section(s)`, "success");
if (configNode) {
await fetchConfig(configNode);
await fetchConfig(configNode, true);
await fetchExistingParams();
setConfigDialogOpen(true);
}
Expand Down Expand Up @@ -840,7 +858,7 @@ const ConfigSection = ({

return (
<Box sx={{mb: 2, width: "100%", display: 'flex', justifyContent: 'flex-end'}}>
<Box sx={{ width: '100%', overflow: 'hidden' }}>
<Box sx={{width: '100%', overflow: 'hidden'}}>
<Button
variant="contained"
size="small"
Expand Down Expand Up @@ -897,9 +915,14 @@ const ConfigSection = ({
</IconButton>
</Tooltip>
</Box>
{!configNode && !configLoading && !configError && (
<Typography color="textSecondary" variant="body2">
No instance selected to view configuration.
</Typography>
)}
{configLoading && <CircularProgress size={24}/>}
{configError && <Alert severity="error" sx={{mb: 2}}>{configError}</Alert>}
{!configLoading && !configError && configData === null && (
{!configLoading && !configError && configData === null && configNode && (
<Typography color="textSecondary" variant="body2">No configuration available.</Typography>
)}
{!configLoading && !configError && configData !== null && (
Expand Down
15 changes: 11 additions & 4 deletions src/components/HeaderSection.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ const HeaderSection = ({
{name: "flip", options: {enabled: true}},
],
sx: {
zIndex: 1300,
zIndex: 10000,
"& .MuiPaper-root": {minWidth: 200, boxShadow: "0px 5px 15px rgba(0,0,0,0.2)"},
},
});
Expand Down Expand Up @@ -129,7 +129,7 @@ const HeaderSection = ({
<Popper open={Boolean(objectMenuAnchor)} anchorEl={objectMenuAnchor} {...popperProps()}>
<ClickAwayListener onClickAway={() => setObjectMenuAnchor(null)}>
<Paper elevation={3} role="menu">
{OBJECT_ACTIONS.map(({name, icon}) => {
{OBJECT_ACTIONS.map(({name, icon, color}) => {
const isAllowed = isActionAllowedForSelection(name, [decodedObjectName]);
return (
<MenuItem
Expand All @@ -140,13 +140,20 @@ const HeaderSection = ({
}}
disabled={!isAllowed || actionInProgress}
sx={{
color: isAllowed ? 'inherit' : 'text.disabled',
color: isAllowed
? (color === "red" ? "error.main" : "inherit")
: "text.disabled",
'&.Mui-disabled': {opacity: 0.5},
}}
aria-label={`Object ${name} action`}
>
<ListItemIcon
sx={{minWidth: 40, color: isAllowed ? 'inherit' : 'text.disabled'}}>
sx={{
minWidth: 40,
color: isAllowed
? (color === "red" ? "error.main" : "inherit")
: "text.disabled"
}}>
{icon}
</ListItemIcon>
<ListItemText>
Expand Down
26 changes: 13 additions & 13 deletions src/components/InstanceCard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,19 @@ import {grey, blue, red} from "@mui/material/colors";
import logger from '../utils/logger.js';

const InstanceCard = ({
node,
nodeData = {},
selectedNodes = [],
toggleNode = () => logger.warn("toggleNode not provided"),
actionInProgress = false,
setIndividualNodeMenuAnchor = () => logger.warn("setIndividualNodeMenuAnchor not provided"),
setCurrentNode = () => logger.warn("setCurrentNode not provided"),
getColor = () => grey[500],
getNodeState = () => ({avail: "unknown", frozen: "unfrozen", state: null}),
instanceName,
onOpenLogs = () => logger.warn("onOpenLogs not provided"),
onViewInstance,
}) => {
node,
nodeData = {},
selectedNodes = [],
toggleNode = () => logger.warn("toggleNode not provided"),
actionInProgress = false,
setIndividualNodeMenuAnchor = () => logger.warn("setIndividualNodeMenuAnchor not provided"),
setCurrentNode = () => logger.warn("setCurrentNode not provided"),
getColor = () => grey[500],
getNodeState = () => ({avail: "unknown", frozen: "unfrozen", state: null}),
instanceName,
onOpenLogs = () => logger.warn("onOpenLogs not provided"),
onViewInstance,
}) => {
const resolvedInstanceName = instanceName || nodeData?.instanceName || nodeData?.name || node;
const [isHovered, setIsHovered] = useState(false);

Expand Down
17 changes: 14 additions & 3 deletions src/components/NodeRow.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -264,19 +264,30 @@ const NodeRow = ({
open={Boolean(anchorEl)}
onClose={() => onMenuClose(nodename)}
{...menuProps}
sx={{
...(menuProps.sx || {}),
zIndex: 10000,
}}
>
{filteredMenuItems.map(({name, icon}) => (
{filteredMenuItems.map(({name, icon, color}) => (
<MenuItem
key={name}
onClick={(e) => {
e.stopPropagation();
onAction(nodename, name);
onMenuClose(nodename);
}}
sx={{display: "flex", alignItems: "center", gap: 1}}
sx={{
display: "flex",
alignItems: "center",
gap: 1,
color: color === "red" ? "error.main" : "inherit",
}}
aria-label={`${name} action`}
>
<ListItemIcon>{icon}</ListItemIcon>
<ListItemIcon sx={{color: color === "red" ? "error.main" : "inherit"}}>
{icon}
</ListItemIcon>
<ListItemText>
{name
.split(" ")
Expand Down
12 changes: 9 additions & 3 deletions src/components/NodesTable.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -425,19 +425,25 @@ const NodesTable = () => {
vertical: "top",
horizontal: "right",
}}
sx={menuSx}
sx={{
...menuSx,
zIndex: 10000,
}}
>
{filteredMenuItems.map(({name, icon}) => (
{filteredMenuItems.map(({name, icon, color}) => (
<MenuItem
key={name}
onClick={() => handleAction(name)}
sx={{
display: "flex",
alignItems: "center",
gap: 1,
color: color === "red" ? "error.main" : "inherit",
}}
>
<ListItemIcon>{icon}</ListItemIcon>
<ListItemIcon sx={{color: color === "red" ? "error.main" : "inherit"}}>
{icon}
</ListItemIcon>
<ListItemText>
{name
.split(" ")
Expand Down
Loading
Loading