
* WIP: add scheduler commands * refactor scheduler commands page * WIP on CRUD for ScheduledCommands * more refactoring, finish CRUD On scheduled pages * Add info text about Scheduler service * Finish recurring commands add/edit * add more texts * fix server status behavior on not-loaded-yet state; adjust texts; lint fixes * add link to the help pages in the commands palette * Move Commands Panel to ServerSchedulesPage * Rename Server Schedules to Server Actions * more texts, a bit changed visual of the actions page, lint fix * add docs * fix tests * Add UTC label to scheduled command create/edit
242 lines
6.6 KiB
TypeScript
242 lines
6.6 KiB
TypeScript
import MonitorHeartIcon from "@mui/icons-material/MonitorHeart";
|
|
import { Avatar, Badge, Box, Theme, Tooltip } from "@mui/material";
|
|
import { BadgeProps } from "@mui/material/Badge";
|
|
import { styled } from "@mui/material/styles";
|
|
import { useTheme } from "@mui/material/styles";
|
|
import { useEffect } from "react";
|
|
import { Button, useDataProvider, useStore } from "react-admin";
|
|
import { useNavigate } from "react-router";
|
|
|
|
import { useAppContext } from "../../Context";
|
|
import { ServerProcessResponse, ServerStatusResponse } from "../../synapse/dataProvider";
|
|
|
|
interface StyledBadgeProps extends BadgeProps {
|
|
backgroundColor: string;
|
|
badgeColor: string;
|
|
theme?: Theme;
|
|
}
|
|
|
|
const StyledBadge = styled(Badge, {
|
|
shouldForwardProp: prop => !["badgeColor", "backgroundColor"].includes(prop as string),
|
|
})<StyledBadgeProps>(({ theme, backgroundColor, badgeColor }) => ({
|
|
"& .MuiBadge-badge": {
|
|
backgroundColor: backgroundColor,
|
|
color: badgeColor,
|
|
boxShadow: `0 0 0 2px ${theme.palette.background.paper}`,
|
|
"&::after": {
|
|
position: "absolute",
|
|
top: 0,
|
|
left: 0,
|
|
width: "100%",
|
|
height: "100%",
|
|
borderRadius: "50%",
|
|
animation: "ripple 2.5s infinite ease-in-out",
|
|
border: "1px solid currentColor",
|
|
content: '""',
|
|
},
|
|
},
|
|
"@keyframes ripple": {
|
|
"0%": {
|
|
transform: "scale(.8)",
|
|
opacity: 1,
|
|
},
|
|
"100%": {
|
|
transform: "scale(2.4)",
|
|
opacity: 0,
|
|
},
|
|
},
|
|
}));
|
|
|
|
// every 5 minutes
|
|
const SERVER_STATUS_INTERVAL_TIME = 5 * 60 * 1000;
|
|
// every 5 seconds
|
|
const SERVER_CURRENT_PROCCESS_INTERVAL_TIME = 5 * 1000;
|
|
|
|
const useServerStatus = () => {
|
|
const [serverStatus, setServerStatus] = useStore<ServerStatusResponse>("serverStatus", {
|
|
ok: false,
|
|
success: false,
|
|
host: "",
|
|
results: [],
|
|
});
|
|
const [serverProcess, setServerProcess] = useStore<ServerProcessResponse>("serverProcess", {
|
|
command: "",
|
|
locked_at: "",
|
|
});
|
|
const { command, locked_at } = serverProcess;
|
|
const { etkeccAdmin } = useAppContext();
|
|
const dataProvider = useDataProvider();
|
|
const isOkay = serverStatus.ok;
|
|
const successCheck = serverStatus.success;
|
|
|
|
const checkServerStatus = async () => {
|
|
const serverStatus: ServerStatusResponse = await dataProvider.getServerStatus(etkeccAdmin, command !== "");
|
|
setServerStatus({
|
|
ok: serverStatus.ok,
|
|
success: serverStatus.success,
|
|
host: serverStatus.host,
|
|
results: serverStatus.results,
|
|
});
|
|
};
|
|
|
|
useEffect(() => {
|
|
let serverStatusInterval: NodeJS.Timeout | null = null;
|
|
let timeoutId: NodeJS.Timeout | null = null;
|
|
|
|
if (etkeccAdmin) {
|
|
checkServerStatus();
|
|
timeoutId = setTimeout(() => {
|
|
// start the interval after 10 seconds to avoid too many requests
|
|
serverStatusInterval = setInterval(checkServerStatus, SERVER_STATUS_INTERVAL_TIME);
|
|
}, 10000);
|
|
} else {
|
|
setServerStatus({ ok: false, success: false, host: "", results: [] });
|
|
}
|
|
|
|
return () => {
|
|
if (timeoutId) {
|
|
clearTimeout(timeoutId);
|
|
}
|
|
if (serverStatusInterval) {
|
|
clearInterval(serverStatusInterval);
|
|
}
|
|
};
|
|
}, [etkeccAdmin, command]);
|
|
|
|
return { isOkay, successCheck };
|
|
};
|
|
|
|
const useCurrentServerProcess = () => {
|
|
const [serverProcess, setServerProcess] = useStore<ServerProcessResponse>("serverProcess", {
|
|
command: "",
|
|
locked_at: "",
|
|
});
|
|
const { etkeccAdmin } = useAppContext();
|
|
const dataProvider = useDataProvider();
|
|
const { command, locked_at } = serverProcess;
|
|
|
|
const checkServerRunningProcess = async () => {
|
|
const serverProcess: ServerProcessResponse = await dataProvider.getServerRunningProcess(
|
|
etkeccAdmin,
|
|
command !== ""
|
|
);
|
|
setServerProcess({
|
|
...serverProcess,
|
|
command: serverProcess.command,
|
|
locked_at: serverProcess.locked_at,
|
|
});
|
|
};
|
|
|
|
useEffect(() => {
|
|
let serverCheckInterval: NodeJS.Timeout | null = null;
|
|
let timeoutId: NodeJS.Timeout | null = null;
|
|
|
|
if (etkeccAdmin) {
|
|
checkServerRunningProcess();
|
|
timeoutId = setTimeout(() => {
|
|
serverCheckInterval = setInterval(checkServerRunningProcess, SERVER_CURRENT_PROCCESS_INTERVAL_TIME);
|
|
}, 5000);
|
|
} else {
|
|
setServerProcess({ command: "", locked_at: "" });
|
|
}
|
|
|
|
return () => {
|
|
if (timeoutId) {
|
|
clearTimeout(timeoutId);
|
|
}
|
|
if (serverCheckInterval) {
|
|
clearInterval(serverCheckInterval);
|
|
}
|
|
};
|
|
}, [etkeccAdmin, command]);
|
|
|
|
return { command, locked_at };
|
|
};
|
|
|
|
export const ServerStatusStyledBadge = ({
|
|
command,
|
|
locked_at,
|
|
isOkay,
|
|
isLoaded,
|
|
inSidebar = false,
|
|
}: {
|
|
command: string;
|
|
locked_at: string;
|
|
isOkay: boolean;
|
|
isLoaded: boolean;
|
|
inSidebar: boolean;
|
|
}) => {
|
|
const theme = useTheme();
|
|
let badgeBackgroundColor = isLoaded
|
|
? isOkay
|
|
? theme.palette.success.light
|
|
: theme.palette.error.main
|
|
: theme.palette.grey[600];
|
|
let badgeColor = isLoaded
|
|
? isOkay
|
|
? theme.palette.success.light
|
|
: theme.palette.error.main
|
|
: theme.palette.grey[600];
|
|
|
|
if (command && locked_at) {
|
|
badgeBackgroundColor = theme.palette.warning.main;
|
|
badgeColor = theme.palette.warning.main;
|
|
}
|
|
let avatarBackgroundColor = theme.palette.mode === "dark" ? theme.palette.background.default : "#2196f3";
|
|
if (inSidebar) {
|
|
avatarBackgroundColor = theme.palette.grey[600];
|
|
}
|
|
|
|
return (
|
|
<StyledBadge
|
|
overlap="circular"
|
|
anchorOrigin={{ vertical: "bottom", horizontal: "right" }}
|
|
variant="dot"
|
|
backgroundColor={badgeBackgroundColor}
|
|
badgeColor={badgeColor}
|
|
>
|
|
<Avatar sx={{ height: 24, width: 24, background: avatarBackgroundColor }}>
|
|
<MonitorHeartIcon sx={{ height: 22, width: 22, color: theme.palette.common.white }} />
|
|
</Avatar>
|
|
</StyledBadge>
|
|
);
|
|
};
|
|
|
|
const ServerStatusBadge = () => {
|
|
const { isOkay, successCheck } = useServerStatus();
|
|
const { command, locked_at } = useCurrentServerProcess();
|
|
const navigate = useNavigate();
|
|
|
|
if (!successCheck) {
|
|
return null;
|
|
}
|
|
|
|
const handleServerStatusClick = () => {
|
|
navigate("/server_status");
|
|
};
|
|
|
|
let tooltipText = "Click to view Server Status";
|
|
|
|
if (command && locked_at) {
|
|
tooltipText = `Running: ${command}; ${tooltipText}`;
|
|
}
|
|
|
|
return (
|
|
<Button onClick={handleServerStatusClick} size="medium" sx={{ minWidth: "auto", ".MuiButton-startIcon": { m: 0 } }}>
|
|
<Tooltip title={tooltipText} sx={{ cursor: "pointer" }}>
|
|
<Box>
|
|
<ServerStatusStyledBadge
|
|
inSidebar={false}
|
|
command={command || ""}
|
|
locked_at={locked_at || ""}
|
|
isOkay={isOkay}
|
|
isLoaded={successCheck}
|
|
/>
|
|
</Box>
|
|
</Tooltip>
|
|
</Button>
|
|
);
|
|
};
|
|
|
|
export default ServerStatusBadge;
|