remove unused eslint plugin, run eslint --fix, rollback node memory workaround in ci
This commit is contained in:
@@ -1,131 +1,147 @@
|
||||
import { PlayArrow, CheckCircle } from "@mui/icons-material";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TableRow,
|
||||
Paper,
|
||||
Alert,
|
||||
TextField,
|
||||
Box,
|
||||
} from "@mui/material";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Button, Loading, useDataProvider, useCreatePath, useStore } from "react-admin";
|
||||
import { useAppContext } from "../../Context";
|
||||
import { Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper, Alert, TextField, Box } from "@mui/material";
|
||||
import { PlayArrow, CheckCircle } from "@mui/icons-material";
|
||||
import { Icons } from "../../utils/icons";
|
||||
import { ServerCommand, ServerProcessResponse } from "../../synapse/dataProvider";
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
import { useAppContext } from "../../Context";
|
||||
import { ServerCommand, ServerProcessResponse } from "../../synapse/dataProvider";
|
||||
import { Icons } from "../../utils/icons";
|
||||
|
||||
const renderIcon = (icon: string) => {
|
||||
const IconComponent = Icons[icon] as React.ComponentType<any> | undefined;
|
||||
return IconComponent ? <IconComponent sx={{ verticalAlign: "middle", mr: 1 }} /> : null;
|
||||
}
|
||||
};
|
||||
|
||||
const ServerCommandsPanel = () => {
|
||||
const { etkeccAdmin } = useAppContext();
|
||||
const createPath = useCreatePath();
|
||||
|
||||
const [ isLoading, setLoading ] = useState(true);
|
||||
const [serverCommands, setServerCommands] = useState<{ [key: string]: ServerCommand }>({});
|
||||
const [serverProcess, setServerProcess] = useStore<ServerProcessResponse>("serverProcess", { command: "", locked_at: "" });
|
||||
const [commandIsRunning, setCommandIsRunning] = useState<boolean>(serverProcess.command !== "");
|
||||
const [commandResult, setCommandResult] = useState<any[]>([]);
|
||||
const [isLoading, setLoading] = useState(true);
|
||||
const [serverCommands, setServerCommands] = useState<Record<string, ServerCommand>>({});
|
||||
const [serverProcess, setServerProcess] = useStore<ServerProcessResponse>("serverProcess", {
|
||||
command: "",
|
||||
locked_at: "",
|
||||
});
|
||||
const [commandIsRunning, setCommandIsRunning] = useState<boolean>(serverProcess.command !== "");
|
||||
const [commandResult, setCommandResult] = useState<any[]>([]);
|
||||
|
||||
const dataProvider = useDataProvider();
|
||||
const dataProvider = useDataProvider();
|
||||
|
||||
useEffect(() => {
|
||||
const fetchIsAdmin = async () => {
|
||||
const serverCommandsResponse = await dataProvider.getServerCommands(etkeccAdmin);
|
||||
if (serverCommandsResponse) {
|
||||
const serverCommands = serverCommandsResponse;
|
||||
Object.keys(serverCommandsResponse).forEach((command: string) => {
|
||||
serverCommands[command].additionalArgs = "";
|
||||
});
|
||||
setServerCommands(serverCommands);
|
||||
}
|
||||
setLoading(false);
|
||||
useEffect(() => {
|
||||
const fetchIsAdmin = async () => {
|
||||
const serverCommandsResponse = await dataProvider.getServerCommands(etkeccAdmin);
|
||||
if (serverCommandsResponse) {
|
||||
const serverCommands = serverCommandsResponse;
|
||||
Object.keys(serverCommandsResponse).forEach((command: string) => {
|
||||
serverCommands[command].additionalArgs = "";
|
||||
});
|
||||
setServerCommands(serverCommands);
|
||||
}
|
||||
fetchIsAdmin();
|
||||
}, []);
|
||||
setLoading(false);
|
||||
};
|
||||
fetchIsAdmin();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (serverProcess.command === "") {
|
||||
useEffect(() => {
|
||||
if (serverProcess.command === "") {
|
||||
setCommandIsRunning(false);
|
||||
}
|
||||
}, [serverProcess]);
|
||||
|
||||
const setCommandAdditionalArgs = (command: string, additionalArgs: string) => {
|
||||
const updatedServerCommands = { ...serverCommands };
|
||||
updatedServerCommands[command].additionalArgs = additionalArgs;
|
||||
setServerCommands(updatedServerCommands);
|
||||
};
|
||||
|
||||
const runCommand = async (command: string) => {
|
||||
setCommandResult([]);
|
||||
setCommandIsRunning(true);
|
||||
|
||||
try {
|
||||
const additionalArgs = serverCommands[command].additionalArgs || "";
|
||||
const requestParams = additionalArgs ? { args: additionalArgs } : {};
|
||||
|
||||
const response = await dataProvider.runServerCommand(etkeccAdmin, command, requestParams);
|
||||
|
||||
if (!response.success) {
|
||||
setCommandIsRunning(false);
|
||||
return;
|
||||
}
|
||||
}, [serverProcess]);
|
||||
|
||||
const setCommandAdditionalArgs = (command: string, additionalArgs: string) => {
|
||||
const updatedServerCommands = {...serverCommands};
|
||||
updatedServerCommands[command].additionalArgs = additionalArgs;
|
||||
setServerCommands(updatedServerCommands);
|
||||
// Update UI with success message
|
||||
const commandResults = buildCommandResultMessages(command, additionalArgs);
|
||||
setCommandResult(commandResults);
|
||||
|
||||
// Reset the additional args field
|
||||
resetCommandArgs(command);
|
||||
|
||||
// Update server process status
|
||||
await updateServerProcessStatus(serverCommands[command]);
|
||||
} catch (error) {
|
||||
setCommandIsRunning(false);
|
||||
}
|
||||
};
|
||||
|
||||
const buildCommandResultMessages = (command: string, additionalArgs: string): React.ReactNode[] => {
|
||||
const results: React.ReactNode[] = [];
|
||||
|
||||
let commandScheduledText = `Command scheduled: ${command}`;
|
||||
if (additionalArgs) {
|
||||
commandScheduledText += `, with additional args: ${additionalArgs}`;
|
||||
}
|
||||
|
||||
const runCommand = async (command: string) => {
|
||||
setCommandResult([]);
|
||||
setCommandIsRunning(true);
|
||||
results.push(<Box>{commandScheduledText}</Box>);
|
||||
results.push(
|
||||
<Box>
|
||||
Expect your result in the{" "}
|
||||
<Link to={createPath({ resource: "server_notifications", type: "list" })}>Notifications</Link> page soon.
|
||||
</Box>
|
||||
);
|
||||
|
||||
try {
|
||||
const additionalArgs = serverCommands[command].additionalArgs || "";
|
||||
const requestParams = additionalArgs ? { args: additionalArgs } : {};
|
||||
return results;
|
||||
};
|
||||
|
||||
const response = await dataProvider.runServerCommand(etkeccAdmin, command, requestParams);
|
||||
const resetCommandArgs = (command: string) => {
|
||||
const updatedServerCommands = { ...serverCommands };
|
||||
updatedServerCommands[command].additionalArgs = "";
|
||||
setServerCommands(updatedServerCommands);
|
||||
};
|
||||
|
||||
if (!response.success) {
|
||||
setCommandIsRunning(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update UI with success message
|
||||
const commandResults = buildCommandResultMessages(command, additionalArgs);
|
||||
setCommandResult(commandResults);
|
||||
|
||||
// Reset the additional args field
|
||||
resetCommandArgs(command);
|
||||
|
||||
// Update server process status
|
||||
await updateServerProcessStatus(serverCommands[command]);
|
||||
} catch (error) {
|
||||
setCommandIsRunning(false);
|
||||
}
|
||||
};
|
||||
|
||||
const buildCommandResultMessages = (command: string, additionalArgs: string): React.ReactNode[] => {
|
||||
const results: React.ReactNode[] = [];
|
||||
|
||||
let commandScheduledText = `Command scheduled: ${command}`;
|
||||
if (additionalArgs) {
|
||||
commandScheduledText += `, with additional args: ${additionalArgs}`;
|
||||
}
|
||||
|
||||
results.push(<Box>{commandScheduledText}</Box>);
|
||||
results.push(
|
||||
<Box>
|
||||
Expect your result in the <Link to={createPath({ resource: "server_notifications", type: "list" })}>Notifications</Link> page soon.
|
||||
</Box>
|
||||
);
|
||||
|
||||
return results;
|
||||
};
|
||||
|
||||
const resetCommandArgs = (command: string) => {
|
||||
const updatedServerCommands = {...serverCommands};
|
||||
updatedServerCommands[command].additionalArgs = "";
|
||||
setServerCommands(updatedServerCommands);
|
||||
};
|
||||
|
||||
const updateServerProcessStatus = async (command: ServerCommand) => {
|
||||
const commandIsLocking = command.with_lock;
|
||||
const serverProcess = await dataProvider.getServerRunningProcess(etkeccAdmin, true);
|
||||
if (!commandIsLocking && serverProcess.command === "") {
|
||||
// if command is not locking, we simulate the "lock" mechanism so notifications will be refetched
|
||||
serverProcess["command"] = command.name;
|
||||
serverProcess["locked_at"] = new Date().toISOString();
|
||||
}
|
||||
|
||||
setServerProcess({...serverProcess});
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return <Loading />
|
||||
const updateServerProcessStatus = async (command: ServerCommand) => {
|
||||
const commandIsLocking = command.with_lock;
|
||||
const serverProcess = await dataProvider.getServerRunningProcess(etkeccAdmin, true);
|
||||
if (!commandIsLocking && serverProcess.command === "") {
|
||||
// if command is not locking, we simulate the "lock" mechanism so notifications will be refetched
|
||||
serverProcess["command"] = command.name;
|
||||
serverProcess["locked_at"] = new Date().toISOString();
|
||||
}
|
||||
|
||||
return (<>
|
||||
<h2>Server Commands</h2>
|
||||
<TableContainer component={Paper}>
|
||||
<Table sx={{ minWidth: 450 }} size="small" aria-label="simple table">
|
||||
<TableHead>
|
||||
setServerProcess({ ...serverProcess });
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<h2>Server Commands</h2>
|
||||
<TableContainer component={Paper}>
|
||||
<Table sx={{ minWidth: 450 }} size="small" aria-label="simple table">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Command</TableCell>
|
||||
<TableCell>Description</TableCell>
|
||||
@@ -134,10 +150,7 @@ const ServerCommandsPanel = () => {
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{Object.entries(serverCommands).map(([command, { icon, args, description, additionalArgs }]) => (
|
||||
<TableRow
|
||||
key={command}
|
||||
sx={{ '&:last-child td, &:last-child th': { border: 0 } }}
|
||||
>
|
||||
<TableRow key={command} sx={{ "&:last-child td, &:last-child th": { border: 0 } }}>
|
||||
<TableCell scope="row">
|
||||
<Box>
|
||||
{renderIcon(icon)}
|
||||
@@ -146,22 +159,28 @@ const ServerCommandsPanel = () => {
|
||||
</TableCell>
|
||||
<TableCell>{description}</TableCell>
|
||||
<TableCell>
|
||||
{args && <TextField
|
||||
size="small"
|
||||
variant="standard"
|
||||
onChange={(e) => {
|
||||
setCommandAdditionalArgs(command, e.target.value);
|
||||
}}
|
||||
value={additionalArgs}
|
||||
/>}
|
||||
{args && (
|
||||
<TextField
|
||||
size="small"
|
||||
variant="standard"
|
||||
onChange={e => {
|
||||
setCommandAdditionalArgs(command, e.target.value);
|
||||
}}
|
||||
value={additionalArgs}
|
||||
/>
|
||||
)}
|
||||
<Button
|
||||
size="small"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
label="Run"
|
||||
startIcon={<PlayArrow />}
|
||||
onClick={() => { runCommand(command); }}
|
||||
disabled={commandIsRunning || (args && typeof additionalArgs === 'string' && additionalArgs.length === 0)}
|
||||
onClick={() => {
|
||||
runCommand(command);
|
||||
}}
|
||||
disabled={
|
||||
commandIsRunning || (args && typeof additionalArgs === "string" && additionalArgs.length === 0)
|
||||
}
|
||||
></Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
@@ -170,13 +189,15 @@ const ServerCommandsPanel = () => {
|
||||
</Table>
|
||||
</TableContainer>
|
||||
|
||||
{commandResult.length > 0 && <Alert icon={<CheckCircle fontSize="inherit" />} severity="success">
|
||||
{commandResult.map((result, index) => (
|
||||
<div key={index}>{result}</div>
|
||||
))}
|
||||
</Alert>}
|
||||
{commandResult.length > 0 && (
|
||||
<Alert icon={<CheckCircle fontSize="inherit" />} severity="success">
|
||||
{commandResult.map((result, index) => (
|
||||
<div key={index}>{result}</div>
|
||||
))}
|
||||
</Alert>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
export default ServerCommandsPanel;
|
||||
export default ServerCommandsPanel;
|
||||
|
||||
@@ -1,9 +1,26 @@
|
||||
import { Badge, useTheme, Button, Paper, Popper, ClickAwayListener, Box, List, ListItem, ListItemText, Typography, ListSubheader, IconButton, Divider, Tooltip } from "@mui/material";
|
||||
import NotificationsIcon from '@mui/icons-material/Notifications';
|
||||
import DeleteIcon from "@mui/icons-material/Delete";
|
||||
import NotificationsIcon from "@mui/icons-material/Notifications";
|
||||
import {
|
||||
Badge,
|
||||
useTheme,
|
||||
Button,
|
||||
Paper,
|
||||
Popper,
|
||||
ClickAwayListener,
|
||||
Box,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemText,
|
||||
Typography,
|
||||
ListSubheader,
|
||||
IconButton,
|
||||
Divider,
|
||||
Tooltip,
|
||||
} from "@mui/material";
|
||||
import { Fragment, useEffect, useState } from "react";
|
||||
import { useDataProvider, useStore } from "react-admin";
|
||||
import { useNavigate } from "react-router";
|
||||
import { Fragment, useEffect, useState } from "react";
|
||||
|
||||
import { useAppContext } from "../../Context";
|
||||
import { ServerNotificationsResponse, ServerProcessResponse } from "../../synapse/dataProvider";
|
||||
import { getTimeSince } from "../../utils/date";
|
||||
@@ -12,8 +29,14 @@ import { getTimeSince } from "../../utils/date";
|
||||
const SERVER_NOTIFICATIONS_INTERVAL_TIME = 300000;
|
||||
|
||||
const useServerNotifications = () => {
|
||||
const [serverNotifications, setServerNotifications] = useStore<ServerNotificationsResponse>("serverNotifications", { notifications: [], success: false });
|
||||
const [ serverProcess, setServerProcess ] = useStore<ServerProcessResponse>("serverProcess", { command: "", locked_at: "" });
|
||||
const [serverNotifications, setServerNotifications] = useStore<ServerNotificationsResponse>("serverNotifications", {
|
||||
notifications: [],
|
||||
success: false,
|
||||
});
|
||||
const [serverProcess, setServerProcess] = useStore<ServerProcessResponse>("serverProcess", {
|
||||
command: "",
|
||||
locked_at: "",
|
||||
});
|
||||
const { command, locked_at } = serverProcess;
|
||||
|
||||
const { etkeccAdmin } = useAppContext();
|
||||
@@ -21,13 +44,16 @@ const useServerNotifications = () => {
|
||||
const { notifications, success } = serverNotifications;
|
||||
|
||||
const fetchNotifications = async () => {
|
||||
const notificationsResponse: ServerNotificationsResponse = await dataProvider.getServerNotifications(etkeccAdmin, command !== "");
|
||||
const notificationsResponse: ServerNotificationsResponse = await dataProvider.getServerNotifications(
|
||||
etkeccAdmin,
|
||||
command !== ""
|
||||
);
|
||||
const serverNotifications = [...notificationsResponse.notifications];
|
||||
serverNotifications.reverse();
|
||||
setServerNotifications({
|
||||
...notificationsResponse,
|
||||
notifications: serverNotifications,
|
||||
success: notificationsResponse.success
|
||||
success: notificationsResponse.success,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -60,7 +86,7 @@ const useServerNotifications = () => {
|
||||
if (serverNotificationsInterval) {
|
||||
clearInterval(serverNotificationsInterval);
|
||||
}
|
||||
}
|
||||
};
|
||||
}, [etkeccAdmin, command, locked_at]);
|
||||
|
||||
return { success, notifications, deleteServerNotifications };
|
||||
@@ -89,7 +115,7 @@ export const ServerNotificationsBadge = () => {
|
||||
};
|
||||
|
||||
const handleClearAllNotifications = async () => {
|
||||
deleteServerNotifications()
|
||||
deleteServerNotifications();
|
||||
handleClose();
|
||||
};
|
||||
|
||||
@@ -100,20 +126,21 @@ export const ServerNotificationsBadge = () => {
|
||||
return (
|
||||
<Box>
|
||||
<IconButton onClick={handleOpen} sx={{ color: theme.palette.common.white }}>
|
||||
<Tooltip title={notifications && notifications.length > 0 ? `${notifications.length} new notifications` : `No notifications yet`}>
|
||||
{notifications && notifications.length > 0 && (
|
||||
<Badge badgeContent={notifications.length} color="error">
|
||||
<NotificationsIcon />
|
||||
</Badge>
|
||||
) || <NotificationsIcon />}
|
||||
<Tooltip
|
||||
title={
|
||||
notifications && notifications.length > 0
|
||||
? `${notifications.length} new notifications`
|
||||
: `No notifications yet`
|
||||
}
|
||||
>
|
||||
{(notifications && notifications.length > 0 && (
|
||||
<Badge badgeContent={notifications.length} color="error">
|
||||
<NotificationsIcon />
|
||||
</Badge>
|
||||
)) || <NotificationsIcon />}
|
||||
</Tooltip>
|
||||
</IconButton>
|
||||
<Popper
|
||||
open={open}
|
||||
anchorEl={anchorEl}
|
||||
placement="bottom-end"
|
||||
style={{ zIndex: 1300 }}
|
||||
>
|
||||
<Popper open={open} anchorEl={anchorEl} placement="bottom-end" style={{ zIndex: 1300 }}>
|
||||
<ClickAwayListener onClickAway={handleClose}>
|
||||
<Paper
|
||||
elevation={3}
|
||||
@@ -125,12 +152,14 @@ export const ServerNotificationsBadge = () => {
|
||||
minWidth: "300px",
|
||||
maxWidth: {
|
||||
xs: "100vw", // Full width on mobile
|
||||
sm: "400px" // Fixed width on desktop
|
||||
}
|
||||
sm: "400px", // Fixed width on desktop
|
||||
},
|
||||
}}
|
||||
>
|
||||
{(!notifications || notifications.length === 0) ? (
|
||||
<Typography sx={{ p: 1 }} variant="body2">No new notifications</Typography>
|
||||
{!notifications || notifications.length === 0 ? (
|
||||
<Typography sx={{ p: 1 }} variant="body2">
|
||||
No new notifications
|
||||
</Typography>
|
||||
) : (
|
||||
<List sx={{ p: 0 }} dense={true}>
|
||||
<ListSubheader
|
||||
@@ -141,46 +170,55 @@ export const ServerNotificationsBadge = () => {
|
||||
fontWeight: "bold",
|
||||
}}
|
||||
>
|
||||
<Typography variant="h6">Notifications</Typography>
|
||||
<Box sx={{ cursor: "pointer", color: theme.palette.primary.main }} onClick={() => handleSeeAllNotifications()}>See all notifications</Box>
|
||||
</ListSubheader>
|
||||
<Typography variant="h6">Notifications</Typography>
|
||||
<Box
|
||||
sx={{ cursor: "pointer", color: theme.palette.primary.main }}
|
||||
onClick={() => handleSeeAllNotifications()}
|
||||
>
|
||||
See all notifications
|
||||
</Box>
|
||||
</ListSubheader>
|
||||
<Divider />
|
||||
{notifications.map((notification, index) => {
|
||||
return (<Fragment key={notification.event_id ? notification.event_id + index : index }>
|
||||
<ListItem
|
||||
onClick={() => handleSeeAllNotifications()}
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "flex-start",
|
||||
overflow: "hidden",
|
||||
"&:hover": {
|
||||
backgroundColor: "action.hover",
|
||||
cursor: "pointer"
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ListItemText
|
||||
primary={
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
}}
|
||||
dangerouslySetInnerHTML={{ __html: notification.output.split("\n")[0] }}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<ListItemText
|
||||
primary={
|
||||
<Typography variant="body2" sx={{ color: theme.palette.text.secondary }}>{getTimeSince(notification.sent_at) + " ago"}</Typography>
|
||||
}
|
||||
/>
|
||||
</ListItem>
|
||||
<Divider />
|
||||
</Fragment>
|
||||
)})}
|
||||
return (
|
||||
<Fragment key={notification.event_id ? notification.event_id + index : index}>
|
||||
<ListItem
|
||||
onClick={() => handleSeeAllNotifications()}
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "flex-start",
|
||||
overflow: "hidden",
|
||||
"&:hover": {
|
||||
backgroundColor: "action.hover",
|
||||
cursor: "pointer",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<ListItemText
|
||||
primary={
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
}}
|
||||
dangerouslySetInnerHTML={{ __html: notification.output.split("\n")[0] }}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<ListItemText
|
||||
primary={
|
||||
<Typography variant="body2" sx={{ color: theme.palette.text.secondary }}>
|
||||
{getTimeSince(notification.sent_at) + " ago"}
|
||||
</Typography>
|
||||
}
|
||||
/>
|
||||
</ListItem>
|
||||
<Divider />
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
<ListItem>
|
||||
<Button
|
||||
key="clear-all-notifications"
|
||||
@@ -190,7 +228,7 @@ export const ServerNotificationsBadge = () => {
|
||||
sx={{
|
||||
pl: 0,
|
||||
pt: 1,
|
||||
verticalAlign: "middle"
|
||||
verticalAlign: "middle",
|
||||
}}
|
||||
>
|
||||
<DeleteIcon fontSize="small" sx={{ mr: 1 }} />
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { Box, Typography, Paper, Button } from "@mui/material"
|
||||
import { Stack } from "@mui/material"
|
||||
import { useStore } from "react-admin"
|
||||
import dataProvider, { ServerNotificationsResponse } from "../../synapse/dataProvider"
|
||||
import { useAppContext } from "../../Context";
|
||||
import DeleteIcon from "@mui/icons-material/Delete";
|
||||
import { getTimeSince } from "../../utils/date";
|
||||
import { Box, Typography, Paper, Button } from "@mui/material";
|
||||
import { Stack } from "@mui/material";
|
||||
import { Tooltip } from "@mui/material";
|
||||
import { useStore } from "react-admin";
|
||||
|
||||
import { useAppContext } from "../../Context";
|
||||
import dataProvider, { ServerNotificationsResponse } from "../../synapse/dataProvider";
|
||||
import { getTimeSince } from "../../utils/date";
|
||||
|
||||
const DisplayTime = ({ date }: { date: string }) => {
|
||||
const dateFromDateString = new Date(date.replace(" ", "T") + "Z");
|
||||
@@ -15,8 +16,8 @@ const DisplayTime = ({ date }: { date: string }) => {
|
||||
const ServerNotificationsPage = () => {
|
||||
const { etkeccAdmin } = useAppContext();
|
||||
const [serverNotifications, setServerNotifications] = useStore<ServerNotificationsResponse>("serverNotifications", {
|
||||
notifications: [],
|
||||
success: false,
|
||||
notifications: [],
|
||||
success: false,
|
||||
});
|
||||
|
||||
const notifications = serverNotifications.notifications;
|
||||
@@ -26,13 +27,17 @@ const ServerNotificationsPage = () => {
|
||||
<Stack spacing={1} direction="row" alignItems="center">
|
||||
<Box sx={{ display: "flex", alignItems: "center", justifyContent: "space-between", width: "100%", gap: 1 }}>
|
||||
<Typography variant="h4">Server Notifications</Typography>
|
||||
<Button variant="text" color="error" onClick={async () => {
|
||||
await dataProvider.deleteServerNotifications(etkeccAdmin);
|
||||
setServerNotifications({
|
||||
notifications: [],
|
||||
success: true,
|
||||
});
|
||||
}}>
|
||||
<Button
|
||||
variant="text"
|
||||
color="error"
|
||||
onClick={async () => {
|
||||
await dataProvider.deleteServerNotifications(etkeccAdmin);
|
||||
setServerNotifications({
|
||||
notifications: [],
|
||||
success: true,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<DeleteIcon fontSize="small" sx={{ mr: 1 }} /> Clear
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
@@ -1,48 +1,50 @@
|
||||
import MonitorHeartIcon from "@mui/icons-material/MonitorHeart";
|
||||
import { Avatar, Badge, Box, Theme, Tooltip } from "@mui/material";
|
||||
import { useEffect } from "react";
|
||||
import { useAppContext } from "../../Context";
|
||||
import { Button, useDataProvider, useStore } from "react-admin";
|
||||
import { styled } from '@mui/material/styles';
|
||||
import MonitorHeartIcon from '@mui/icons-material/MonitorHeart';
|
||||
import { BadgeProps } from "@mui/material/Badge";
|
||||
import { useNavigate } from "react-router";
|
||||
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
|
||||
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: '""',
|
||||
},
|
||||
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,
|
||||
},
|
||||
},
|
||||
"@keyframes ripple": {
|
||||
"0%": {
|
||||
transform: "scale(.8)",
|
||||
opacity: 1,
|
||||
},
|
||||
"100%": {
|
||||
transform: "scale(2.4)",
|
||||
opacity: 0,
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
// every 5 minutes
|
||||
@@ -51,8 +53,16 @@ const SERVER_STATUS_INTERVAL_TIME = 5 * 60 * 1000;
|
||||
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 [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();
|
||||
@@ -79,7 +89,7 @@ const useServerStatus = () => {
|
||||
// start the interval after 10 seconds to avoid too many requests
|
||||
serverStatusInterval = setInterval(checkServerStatus, SERVER_STATUS_INTERVAL_TIME);
|
||||
}, 10000);
|
||||
} else {
|
||||
} else {
|
||||
setServerStatus({ ok: false, success: false, host: "", results: [] });
|
||||
}
|
||||
|
||||
@@ -90,26 +100,32 @@ const useServerStatus = () => {
|
||||
if (serverStatusInterval) {
|
||||
clearInterval(serverStatusInterval);
|
||||
}
|
||||
}
|
||||
};
|
||||
}, [etkeccAdmin, command]);
|
||||
|
||||
return { isOkay, successCheck };
|
||||
};
|
||||
|
||||
const useCurrentServerProcess = () => {
|
||||
const [serverProcess, setServerProcess] = useStore<ServerProcessResponse>("serverProcess", { command: "", locked_at: "" });
|
||||
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 !== "");
|
||||
const serverProcess: ServerProcessResponse = await dataProvider.getServerRunningProcess(
|
||||
etkeccAdmin,
|
||||
command !== ""
|
||||
);
|
||||
setServerProcess({
|
||||
...serverProcess,
|
||||
command: serverProcess.command,
|
||||
locked_at: serverProcess.locked_at
|
||||
locked_at: serverProcess.locked_at,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
let serverCheckInterval: NodeJS.Timeout | null = null;
|
||||
@@ -131,13 +147,23 @@ const useCurrentServerProcess = () => {
|
||||
if (serverCheckInterval) {
|
||||
clearInterval(serverCheckInterval);
|
||||
}
|
||||
}
|
||||
};
|
||||
}, [etkeccAdmin, command]);
|
||||
|
||||
return { command, locked_at };
|
||||
};
|
||||
|
||||
export const ServerStatusStyledBadge = ({ command, locked_at, isOkay, inSidebar = false }: { command: string, locked_at: string, isOkay: boolean, inSidebar: boolean }) => {
|
||||
export const ServerStatusStyledBadge = ({
|
||||
command,
|
||||
locked_at,
|
||||
isOkay,
|
||||
inSidebar = false,
|
||||
}: {
|
||||
command: string;
|
||||
locked_at: string;
|
||||
isOkay: boolean;
|
||||
inSidebar: boolean;
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
let badgeBackgroundColor = isOkay ? theme.palette.success.light : theme.palette.error.main;
|
||||
let badgeColor = isOkay ? theme.palette.success.light : theme.palette.error.main;
|
||||
@@ -151,9 +177,10 @@ export const ServerStatusStyledBadge = ({ command, locked_at, isOkay, inSidebar
|
||||
avatarBackgroundColor = theme.palette.grey[600];
|
||||
}
|
||||
|
||||
return <StyledBadge
|
||||
return (
|
||||
<StyledBadge
|
||||
overlap="circular"
|
||||
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
|
||||
anchorOrigin={{ vertical: "bottom", horizontal: "right" }}
|
||||
variant="dot"
|
||||
backgroundColor={badgeBackgroundColor}
|
||||
badgeColor={badgeColor}
|
||||
@@ -161,35 +188,43 @@ export const ServerStatusStyledBadge = ({ command, locked_at, isOkay, inSidebar
|
||||
<Avatar sx={{ height: 24, width: 24, background: avatarBackgroundColor }}>
|
||||
<MonitorHeartIcon sx={{ height: 22, width: 22, color: theme.palette.common.white }} />
|
||||
</Avatar>
|
||||
</StyledBadge>
|
||||
</StyledBadge>
|
||||
);
|
||||
};
|
||||
|
||||
const ServerStatusBadge = () => {
|
||||
const { isOkay, successCheck } = useServerStatus();
|
||||
const { command, locked_at } = useCurrentServerProcess();
|
||||
const navigate = useNavigate();
|
||||
const { isOkay, successCheck } = useServerStatus();
|
||||
const { command, locked_at } = useCurrentServerProcess();
|
||||
const navigate = useNavigate();
|
||||
|
||||
if (!successCheck) {
|
||||
return null;
|
||||
}
|
||||
if (!successCheck) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const handleServerStatusClick = () => {
|
||||
navigate("/server_status");
|
||||
};
|
||||
const handleServerStatusClick = () => {
|
||||
navigate("/server_status");
|
||||
};
|
||||
|
||||
let tooltipText = "Click to view Server Status";
|
||||
let tooltipText = "Click to view Server Status";
|
||||
|
||||
if (command && locked_at) {
|
||||
tooltipText = `Running: ${command}; ${tooltipText}`;
|
||||
}
|
||||
if (command && locked_at) {
|
||||
tooltipText = `Running: ${command}; ${tooltipText}`;
|
||||
}
|
||||
|
||||
return <Button onClick={handleServerStatusClick} size="medium" sx={{ minWidth: "auto", ".MuiButton-startIcon": { m: 0 }}}>
|
||||
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} />
|
||||
</Box>
|
||||
<Box>
|
||||
<ServerStatusStyledBadge
|
||||
inSidebar={false}
|
||||
command={command || ""}
|
||||
locked_at={locked_at || ""}
|
||||
isOkay={isOkay}
|
||||
/>
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
export default ServerStatusBadge;
|
||||
|
||||
@@ -1,13 +1,22 @@
|
||||
import { useStore } from "ra-core";
|
||||
import { Box, Stack, Typography, Paper, Link, Chip, Divider, Tooltip, ChipProps } from "@mui/material";
|
||||
import CheckIcon from '@mui/icons-material/Check';
|
||||
import CheckIcon from "@mui/icons-material/Check";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import EngineeringIcon from '@mui/icons-material/Engineering';
|
||||
import { ServerProcessResponse, ServerStatusComponent, ServerStatusResponse } from "../../synapse/dataProvider";
|
||||
import EngineeringIcon from "@mui/icons-material/Engineering";
|
||||
import { Box, Stack, Typography, Paper, Link, Chip, Divider, Tooltip, ChipProps } from "@mui/material";
|
||||
import { useStore } from "ra-core";
|
||||
|
||||
import ServerCommandsPanel from "./ServerCommandsPanel";
|
||||
import { ServerProcessResponse, ServerStatusComponent, ServerStatusResponse } from "../../synapse/dataProvider";
|
||||
import { getTimeSince } from "../../utils/date";
|
||||
|
||||
const StatusChip = ({ isOkay, size = "medium", command }: { isOkay: boolean, size?: "small" | "medium", command?: string }) => {
|
||||
const StatusChip = ({
|
||||
isOkay,
|
||||
size = "medium",
|
||||
command,
|
||||
}: {
|
||||
isOkay: boolean;
|
||||
size?: "small" | "medium";
|
||||
command?: string;
|
||||
}) => {
|
||||
let label = "OK";
|
||||
let icon = <CheckIcon />;
|
||||
let color: ChipProps["color"] = "success";
|
||||
@@ -23,9 +32,7 @@ const StatusChip = ({ isOkay, size = "medium", command }: { isOkay: boolean, siz
|
||||
icon = <EngineeringIcon />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Chip icon={icon} label={label} color={color} variant="outlined" size={size} />
|
||||
);
|
||||
return <Chip icon={icon} label={label} color={color} variant="outlined" size={size} />;
|
||||
};
|
||||
|
||||
const ServerComponentText = ({ text }: { text: string }) => {
|
||||
@@ -39,14 +46,17 @@ const ServerStatusPage = () => {
|
||||
host: "",
|
||||
results: [],
|
||||
});
|
||||
const [ serverProcess, setServerProcess ] = useStore<ServerProcessResponse>("serverProcess", { command: "", locked_at: "" });
|
||||
const [serverProcess, setServerProcess] = useStore<ServerProcessResponse>("serverProcess", {
|
||||
command: "",
|
||||
locked_at: "",
|
||||
});
|
||||
const { command, locked_at } = serverProcess;
|
||||
const successCheck = serverStatus.success;
|
||||
const isOkay = serverStatus.ok;
|
||||
const host = serverStatus.host;
|
||||
const results = serverStatus.results;
|
||||
|
||||
let groupedResults: Record<string, ServerStatusComponent[]> = {};
|
||||
const groupedResults: Record<string, ServerStatusComponent[]> = {};
|
||||
for (const result of results) {
|
||||
if (!groupedResults[result.category]) {
|
||||
groupedResults[result.category] = [];
|
||||
@@ -59,9 +69,7 @@ const ServerStatusPage = () => {
|
||||
<Paper elevation={3} sx={{ p: 3, mt: 3 }}>
|
||||
<Stack direction="row" spacing={2} alignItems="center">
|
||||
<CloseIcon color="error" />
|
||||
<Typography color="error">
|
||||
Unable to fetch server status. Please try again later.
|
||||
</Typography>
|
||||
<Typography color="error">Unable to fetch server status. Please try again later.</Typography>
|
||||
</Stack>
|
||||
</Paper>
|
||||
);
|
||||
@@ -79,19 +87,21 @@ const ServerStatusPage = () => {
|
||||
</Typography>
|
||||
</Stack>
|
||||
{command && locked_at && (
|
||||
<Stack spacing={1} direction="row" alignItems="center">
|
||||
<Box sx={{ display: "flex", alignItems: "center", gap: 1 }}>
|
||||
<Typography variant="h5">Currently running:</Typography>
|
||||
<Typography variant="h5" color="text.secondary">
|
||||
<Link href={"https://etke.cc/help/extras/scheduler/#"+command} target="_blank">
|
||||
{command}
|
||||
</Link>
|
||||
<Stack spacing={1} direction="row" alignItems="center">
|
||||
<Box sx={{ display: "flex", alignItems: "center", gap: 1 }}>
|
||||
<Typography variant="h5">Currently running:</Typography>
|
||||
<Typography variant="h5" color="text.secondary">
|
||||
<Link href={"https://etke.cc/help/extras/scheduler/#" + command} target="_blank">
|
||||
{command}
|
||||
</Link>
|
||||
<Tooltip title={locked_at.toString()}>
|
||||
<Typography component="span" color="text.secondary" sx={{ display: "inline-block", ml: 1 }}>(started {getTimeSince(locked_at)} ago)</Typography>
|
||||
<Typography component="span" color="text.secondary" sx={{ display: "inline-block", ml: 1 }}>
|
||||
(started {getTimeSince(locked_at)} ago)
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
</Typography>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Typography>
|
||||
</Box>
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
<ServerCommandsPanel />
|
||||
@@ -117,8 +127,10 @@ const ServerStatusPage = () => {
|
||||
<ServerComponentText text={result.label.text} />
|
||||
)}
|
||||
</Box>
|
||||
{result.reason && <Typography color="text.secondary" dangerouslySetInnerHTML={{ __html: result.reason }}/>}
|
||||
{(!result.ok && result.help) && (
|
||||
{result.reason && (
|
||||
<Typography color="text.secondary" dangerouslySetInnerHTML={{ __html: result.reason }} />
|
||||
)}
|
||||
{!result.ok && result.help && (
|
||||
<Link href={result.help} target="_blank" rel="noopener noreferrer" sx={{ mt: 1 }}>
|
||||
Learn more
|
||||
</Link>
|
||||
|
||||
Reference in New Issue
Block a user