Add ServerCommandsPanel to ServerStatusPage
This commit is contained in:
parent
fbb578392d
commit
79c46c2f46
@ -1,4 +1,4 @@
|
|||||||
import { CheckForApplicationUpdate, AppBar, TitlePortal, InspectorButton, Confirm, Layout, Logout, Menu, useLogout, UserMenu } from "react-admin";
|
import { CheckForApplicationUpdate, AppBar, TitlePortal, InspectorButton, Confirm, Layout, Logout, Menu, useLogout, UserMenu, useStore } from "react-admin";
|
||||||
import { LoginMethod } from "../pages/LoginPage";
|
import { LoginMethod } from "../pages/LoginPage";
|
||||||
import { useEffect, useState, Suspense } from "react";
|
import { useEffect, useState, Suspense } from "react";
|
||||||
import { Icons, DefaultIcon } from "../utils/icons";
|
import { Icons, DefaultIcon } from "../utils/icons";
|
||||||
@ -6,6 +6,8 @@ import { MenuItem, GetConfig, ClearConfig } from "../utils/config";
|
|||||||
import Footer from "./Footer";
|
import Footer from "./Footer";
|
||||||
import ServerStatusBadge from "./etke.cc/ServerStatusBadge";
|
import ServerStatusBadge from "./etke.cc/ServerStatusBadge";
|
||||||
import { ServerNotificationsBadge } from "./etke.cc/ServerNotificationsBadge";
|
import { ServerNotificationsBadge } from "./etke.cc/ServerNotificationsBadge";
|
||||||
|
import { ServerProcessResponse, ServerStatusResponse } from "../synapse/dataProvider";
|
||||||
|
import { ServerStatusStyledBadge } from "./etke.cc/ServerStatusBadge";
|
||||||
|
|
||||||
const AdminUserMenu = () => {
|
const AdminUserMenu = () => {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
@ -59,10 +61,20 @@ const AdminAppBar = () => {
|
|||||||
const AdminMenu = (props) => {
|
const AdminMenu = (props) => {
|
||||||
const [menu, setMenu] = useState([] as MenuItem[]);
|
const [menu, setMenu] = useState([] as MenuItem[]);
|
||||||
useEffect(() => setMenu(GetConfig().menu), []);
|
useEffect(() => setMenu(GetConfig().menu), []);
|
||||||
|
const [serverProcess, setServerProcess] = useStore<ServerProcessResponse>("serverProcess", { command: "", locked_at: "" });
|
||||||
|
const [serverStatus, setServerStatus] = useStore<ServerStatusResponse>("serverStatus", { success: false, ok: false, host: "", results: [] });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Menu {...props}>
|
<Menu {...props}>
|
||||||
<Menu.ResourceItems />
|
<Menu.ResourceItems />
|
||||||
|
{menu && <Menu.Item to="/server_status" leftIcon={
|
||||||
|
<ServerStatusStyledBadge
|
||||||
|
command={serverProcess.command}
|
||||||
|
locked_at={serverProcess.locked_at}
|
||||||
|
isOkay={serverStatus.ok} />
|
||||||
|
}
|
||||||
|
primaryText="Server Status" />
|
||||||
|
}
|
||||||
{menu && menu.map((item, index) => {
|
{menu && menu.map((item, index) => {
|
||||||
const { url, icon, label } = item;
|
const { url, icon, label } = item;
|
||||||
const IconComponent = Icons[icon] as React.ComponentType<any> | undefined;
|
const IconComponent = Icons[icon] as React.ComponentType<any> | undefined;
|
||||||
|
141
src/components/etke.cc/ServerCommandsPanel.tsx
Normal file
141
src/components/etke.cc/ServerCommandsPanel.tsx
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
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';
|
||||||
|
|
||||||
|
|
||||||
|
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>(false);
|
||||||
|
const [commandResult, setCommandResult] = useState<any[]>([]);
|
||||||
|
|
||||||
|
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] = serverCommandsResponse[command];
|
||||||
|
serverCommands[command].additionalArgs = "";
|
||||||
|
});
|
||||||
|
setServerCommands(serverCommands);
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
fetchIsAdmin();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const setCommandAdditionalArgs = (command: string, additionalArgs: string) => {
|
||||||
|
const updatedServerCommands = {...serverCommands};
|
||||||
|
updatedServerCommands[command].additionalArgs = additionalArgs;
|
||||||
|
setServerCommands(updatedServerCommands);
|
||||||
|
}
|
||||||
|
|
||||||
|
const runCommand = async (command: string) => {
|
||||||
|
setCommandResult([]);
|
||||||
|
setCommandIsRunning(true);
|
||||||
|
|
||||||
|
const response = await dataProvider.runServerCommand(etkeccAdmin, command);
|
||||||
|
|
||||||
|
setCommandIsRunning(false);
|
||||||
|
if (!response.success) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedServerCommands = {...serverCommands};
|
||||||
|
const commandResult: React.ReactNode[] = [];
|
||||||
|
|
||||||
|
let commandScheduledText = `Command scheduled: ${command}`;
|
||||||
|
if (serverCommands[command].additionalArgs) {
|
||||||
|
commandScheduledText += `, with additional args: ${serverCommands[command].additionalArgs}`;
|
||||||
|
}
|
||||||
|
commandResult.push(<Box>{commandScheduledText}</Box>);
|
||||||
|
commandResult.push(<Box>Expect your result in the <Link to={createPath({ resource: "server_notifications", type: "list" })}>Notifications</Link> page soon.</Box>);
|
||||||
|
|
||||||
|
updatedServerCommands[command].additionalArgs = "";
|
||||||
|
setServerCommands(updatedServerCommands);
|
||||||
|
|
||||||
|
setCommandResult(commandResult);
|
||||||
|
|
||||||
|
const serverProcess: ServerProcessResponse = await dataProvider.getServerRunningProcess(etkeccAdmin, true);
|
||||||
|
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>
|
||||||
|
<TableCell></TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{Object.entries(serverCommands).map(([command, { icon, args, description, additionalArgs }]) => (
|
||||||
|
<TableRow
|
||||||
|
key={command}
|
||||||
|
sx={{ '&:last-child td, &:last-child th': { border: 0 } }}
|
||||||
|
>
|
||||||
|
<TableCell scope="row">
|
||||||
|
<Box>
|
||||||
|
{renderIcon(icon)}
|
||||||
|
{command}
|
||||||
|
</Box>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>{description}</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
{args && <TextField
|
||||||
|
size="small"
|
||||||
|
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)}
|
||||||
|
></Button>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
|
||||||
|
{commandResult.length > 0 && <Alert icon={<CheckCircle fontSize="inherit" />} severity="success">
|
||||||
|
{commandResult.map((result, index) => (
|
||||||
|
<div key={index}>{result}</div>
|
||||||
|
))}
|
||||||
|
</Alert>}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ServerCommandsPanel;
|
@ -5,12 +5,16 @@ import { useDataProvider, useStore } from "react-admin";
|
|||||||
import { useNavigate } from "react-router";
|
import { useNavigate } from "react-router";
|
||||||
import { Fragment, useEffect, useState } from "react";
|
import { Fragment, useEffect, useState } from "react";
|
||||||
import { useAppContext } from "../../Context";
|
import { useAppContext } from "../../Context";
|
||||||
import { ServerNotificationsResponse } from "../../synapse/dataProvider";
|
import { ServerNotificationsResponse, ServerProcessResponse } from "../../synapse/dataProvider";
|
||||||
|
|
||||||
|
// 5 minutes
|
||||||
const SERVER_NOTIFICATIONS_INTERVAL_TIME = 300000;
|
const SERVER_NOTIFICATIONS_INTERVAL_TIME = 300000;
|
||||||
|
|
||||||
const useServerNotifications = () => {
|
const useServerNotifications = () => {
|
||||||
const [serverNotifications, setServerNotifications] = useStore<ServerNotificationsResponse>("serverNotifications", { notifications: [], success: false });
|
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();
|
const { etkeccAdmin } = useAppContext();
|
||||||
const dataProvider = useDataProvider();
|
const dataProvider = useDataProvider();
|
||||||
const { notifications, success } = serverNotifications;
|
const { notifications, success } = serverNotifications;
|
||||||
@ -19,7 +23,7 @@ const useServerNotifications = () => {
|
|||||||
const notificationsResponse: ServerNotificationsResponse = await dataProvider.getServerNotifications(etkeccAdmin);
|
const notificationsResponse: ServerNotificationsResponse = await dataProvider.getServerNotifications(etkeccAdmin);
|
||||||
setServerNotifications({
|
setServerNotifications({
|
||||||
...notificationsResponse,
|
...notificationsResponse,
|
||||||
notifications: notificationsResponse.notifications,
|
notifications: notificationsResponse.notifications.reverse(),
|
||||||
success: notificationsResponse.success
|
success: notificationsResponse.success
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -35,21 +39,26 @@ const useServerNotifications = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let serverNotificationsInterval: NodeJS.Timeout;
|
let serverNotificationsInterval: NodeJS.Timeout | null = null;
|
||||||
|
let timeoutId: NodeJS.Timeout | null = null;
|
||||||
|
|
||||||
if (etkeccAdmin) {
|
if (etkeccAdmin) {
|
||||||
fetchNotifications();
|
fetchNotifications();
|
||||||
setTimeout(() => {
|
timeoutId = setTimeout(() => {
|
||||||
// start the interval after the SERVER_NOTIFICATIONS_INTERVAL_TIME to avoid too many requests
|
// start the interval after the SERVER_NOTIFICATIONS_INTERVAL_TIME to avoid too many requests
|
||||||
serverNotificationsInterval = setInterval(fetchNotifications, SERVER_NOTIFICATIONS_INTERVAL_TIME);
|
serverNotificationsInterval = setInterval(fetchNotifications, SERVER_NOTIFICATIONS_INTERVAL_TIME);
|
||||||
}, SERVER_NOTIFICATIONS_INTERVAL_TIME);
|
}, SERVER_NOTIFICATIONS_INTERVAL_TIME);
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
if (timeoutId) {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
}
|
||||||
if (serverNotificationsInterval) {
|
if (serverNotificationsInterval) {
|
||||||
clearInterval(serverNotificationsInterval);
|
clearInterval(serverNotificationsInterval);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [etkeccAdmin]);
|
}, [etkeccAdmin, command, locked_at]);
|
||||||
|
|
||||||
return { success, notifications, deleteServerNotifications };
|
return { success, notifications, deleteServerNotifications };
|
||||||
};
|
};
|
||||||
@ -108,6 +117,7 @@ export const ServerNotificationsBadge = () => {
|
|||||||
sx={{
|
sx={{
|
||||||
p: 1,
|
p: 1,
|
||||||
maxHeight: "350px",
|
maxHeight: "350px",
|
||||||
|
paddingTop: 0,
|
||||||
overflowY: "auto",
|
overflowY: "auto",
|
||||||
minWidth: "300px",
|
minWidth: "300px",
|
||||||
maxWidth: {
|
maxWidth: {
|
||||||
@ -126,7 +136,6 @@ export const ServerNotificationsBadge = () => {
|
|||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
fontWeight: "bold",
|
fontWeight: "bold",
|
||||||
backgroundColor: "inherit",
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography variant="h6">Notifications</Typography>
|
<Typography variant="h6">Notifications</Typography>
|
||||||
@ -134,10 +143,14 @@ export const ServerNotificationsBadge = () => {
|
|||||||
</ListSubheader>
|
</ListSubheader>
|
||||||
<Divider />
|
<Divider />
|
||||||
{notifications.map((notification, index) => {
|
{notifications.map((notification, index) => {
|
||||||
return (<Fragment key={notification.event_id ? notification.event_id : index }>
|
return (<Fragment key={notification.event_id ? notification.event_id + index : index }>
|
||||||
<ListItem
|
<ListItem
|
||||||
onClick={() => handleSeeAllNotifications()}
|
onClick={() => handleSeeAllNotifications()}
|
||||||
sx={{
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
alignItems: "flex-start",
|
||||||
|
overflow: "hidden",
|
||||||
"&:hover": {
|
"&:hover": {
|
||||||
backgroundColor: "action.hover",
|
backgroundColor: "action.hover",
|
||||||
cursor: "pointer"
|
cursor: "pointer"
|
||||||
@ -157,6 +170,11 @@ export const ServerNotificationsBadge = () => {
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
<ListItemText
|
||||||
|
primary={
|
||||||
|
<Typography variant="body2">{notification.sent_at}</Typography>
|
||||||
|
}
|
||||||
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
<Divider />
|
<Divider />
|
||||||
</Fragment>
|
</Fragment>
|
||||||
|
@ -68,10 +68,12 @@ const useServerStatus = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let serverStatusInterval: NodeJS.Timeout;
|
let serverStatusInterval: NodeJS.Timeout | null = null;
|
||||||
|
let timeoutId: NodeJS.Timeout | null = null;
|
||||||
|
|
||||||
if (etkeccAdmin) {
|
if (etkeccAdmin) {
|
||||||
checkServerStatus();
|
checkServerStatus();
|
||||||
setTimeout(() => {
|
timeoutId = setTimeout(() => {
|
||||||
// start the interval after 10 seconds to avoid too many requests
|
// start the interval after 10 seconds to avoid too many requests
|
||||||
serverStatusInterval = setInterval(checkServerStatus, SERVER_STATUS_INTERVAL_TIME);
|
serverStatusInterval = setInterval(checkServerStatus, SERVER_STATUS_INTERVAL_TIME);
|
||||||
}, 10000);
|
}, 10000);
|
||||||
@ -80,6 +82,9 @@ const useServerStatus = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
if (timeoutId) {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
}
|
||||||
if (serverStatusInterval) {
|
if (serverStatusInterval) {
|
||||||
clearInterval(serverStatusInterval);
|
clearInterval(serverStatusInterval);
|
||||||
}
|
}
|
||||||
@ -105,10 +110,12 @@ const useCurrentServerProcess = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let serverCheckInterval: NodeJS.Timeout;
|
let serverCheckInterval: NodeJS.Timeout | null = null;
|
||||||
|
let timeoutId: NodeJS.Timeout | null = null;
|
||||||
|
|
||||||
if (etkeccAdmin) {
|
if (etkeccAdmin) {
|
||||||
checkServerRunningProcess();
|
checkServerRunningProcess();
|
||||||
setTimeout(() => {
|
timeoutId = setTimeout(() => {
|
||||||
serverCheckInterval = setInterval(checkServerRunningProcess, SERVER_CURRENT_PROCCESS_INTERVAL_TIME);
|
serverCheckInterval = setInterval(checkServerRunningProcess, SERVER_CURRENT_PROCCESS_INTERVAL_TIME);
|
||||||
}, 5000);
|
}, 5000);
|
||||||
} else {
|
} else {
|
||||||
@ -116,6 +123,9 @@ const useCurrentServerProcess = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
if (timeoutId) {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
}
|
||||||
if (serverCheckInterval) {
|
if (serverCheckInterval) {
|
||||||
clearInterval(serverCheckInterval);
|
clearInterval(serverCheckInterval);
|
||||||
}
|
}
|
||||||
@ -125,10 +135,32 @@ const useCurrentServerProcess = () => {
|
|||||||
return { command, locked_at };
|
return { command, locked_at };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const ServerStatusStyledBadge = ({ command, locked_at, isOkay }: { command: string, locked_at: string, isOkay: 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;
|
||||||
|
|
||||||
|
if (command && locked_at) {
|
||||||
|
badgeBackgroundColor = theme.palette.warning.main;
|
||||||
|
badgeColor = theme.palette.warning.main;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <StyledBadge
|
||||||
|
overlap="circular"
|
||||||
|
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
|
||||||
|
variant="dot"
|
||||||
|
backgroundColor={badgeBackgroundColor}
|
||||||
|
badgeColor={badgeColor}
|
||||||
|
>
|
||||||
|
<Avatar sx={{ height: 24, width: 24, background: theme.palette.mode === "dark" ? theme.palette.background.default : "#2196f3" }}>
|
||||||
|
<MonitorHeartIcon sx={{ height: 22, width: 22, color: theme.palette.common.white }} />
|
||||||
|
</Avatar>
|
||||||
|
</StyledBadge>
|
||||||
|
};
|
||||||
|
|
||||||
const ServerStatusBadge = () => {
|
const ServerStatusBadge = () => {
|
||||||
const { isOkay, successCheck } = useServerStatus();
|
const { isOkay, successCheck } = useServerStatus();
|
||||||
const { command, locked_at } = useCurrentServerProcess();
|
const { command, locked_at } = useCurrentServerProcess();
|
||||||
const theme = useTheme();
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
if (!successCheck) {
|
if (!successCheck) {
|
||||||
@ -140,28 +172,14 @@ const ServerStatusBadge = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let tooltipText = "Click to view Server Status";
|
let tooltipText = "Click to view Server Status";
|
||||||
let badgeBackgroundColor = isOkay ? theme.palette.success.light : theme.palette.error.main;
|
|
||||||
let badgeColor = isOkay ? theme.palette.success.light : theme.palette.error.main;
|
|
||||||
|
|
||||||
if (command && locked_at) {
|
if (command && locked_at) {
|
||||||
badgeBackgroundColor = theme.palette.warning.main;
|
|
||||||
badgeColor = theme.palette.warning.main;
|
|
||||||
tooltipText = `Running: ${command}; ${tooltipText}`;
|
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" }}>
|
<Tooltip title={tooltipText} sx={{ cursor: "pointer" }}>
|
||||||
<StyledBadge
|
<ServerStatusStyledBadge command={command || ""} locked_at={locked_at || ""} isOkay={isOkay} />
|
||||||
overlap="circular"
|
|
||||||
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
|
|
||||||
variant="dot"
|
|
||||||
backgroundColor={badgeBackgroundColor}
|
|
||||||
badgeColor={badgeColor}
|
|
||||||
>
|
|
||||||
<Avatar sx={{ height: 24, width: 24, background: theme.palette.mode === "dark" ? theme.palette.background.default : "#2196f3" }}>
|
|
||||||
<MonitorHeartIcon sx={{ height: 22, width: 22, color: theme.palette.common.white }} />
|
|
||||||
</Avatar>
|
|
||||||
</StyledBadge>
|
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Button>
|
</Button>
|
||||||
};
|
};
|
||||||
|
@ -4,6 +4,7 @@ import CheckIcon from '@mui/icons-material/Check';
|
|||||||
import CloseIcon from "@mui/icons-material/Close";
|
import CloseIcon from "@mui/icons-material/Close";
|
||||||
import EngineeringIcon from '@mui/icons-material/Engineering';
|
import EngineeringIcon from '@mui/icons-material/Engineering';
|
||||||
import { ServerProcessResponse, ServerStatusComponent, ServerStatusResponse } from "../../synapse/dataProvider";
|
import { ServerProcessResponse, ServerStatusComponent, ServerStatusResponse } from "../../synapse/dataProvider";
|
||||||
|
import ServerCommandsPanel from "./ServerCommandsPanel";
|
||||||
|
|
||||||
const getTimeSince = (date: string) => {
|
const getTimeSince = (date: string) => {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
@ -102,6 +103,8 @@ const ServerStatusPage = () => {
|
|||||||
</Stack>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<ServerCommandsPanel />
|
||||||
|
|
||||||
<Stack spacing={2} direction="row">
|
<Stack spacing={2} direction="row">
|
||||||
{Object.keys(groupedResults).map((category, idx) => (
|
{Object.keys(groupedResults).map((category, idx) => (
|
||||||
<Box key={`category_${category}`} sx={{ flex: 1 }}>
|
<Box key={`category_${category}`} sx={{ flex: 1 }}>
|
||||||
|
@ -300,8 +300,8 @@ export interface ServerStatusResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ServerProcessResponse {
|
export interface ServerProcessResponse {
|
||||||
locked_at?: string;
|
locked_at: string;
|
||||||
command?: string;
|
command: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ServerNotification {
|
export interface ServerNotification {
|
||||||
@ -315,6 +315,18 @@ export interface ServerNotificationsResponse {
|
|||||||
notifications: ServerNotification[];
|
notifications: ServerNotification[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ServerCommand {
|
||||||
|
icon: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
args: boolean;
|
||||||
|
additionalArgs?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ServerCommandsResponse {
|
||||||
|
[command: string]: ServerCommand;
|
||||||
|
}
|
||||||
|
|
||||||
export interface SynapseDataProvider extends DataProvider {
|
export interface SynapseDataProvider extends DataProvider {
|
||||||
deleteMedia: (params: DeleteMediaParams) => Promise<DeleteMediaResult>;
|
deleteMedia: (params: DeleteMediaParams) => Promise<DeleteMediaResult>;
|
||||||
purgeRemoteMedia: (params: DeleteMediaParams) => Promise<DeleteMediaResult>;
|
purgeRemoteMedia: (params: DeleteMediaParams) => Promise<DeleteMediaResult>;
|
||||||
@ -329,6 +341,7 @@ export interface SynapseDataProvider extends DataProvider {
|
|||||||
getServerStatus: (etkeAdminUrl: string) => Promise<ServerStatusResponse>;
|
getServerStatus: (etkeAdminUrl: string) => Promise<ServerStatusResponse>;
|
||||||
getServerNotifications: (etkeAdminUrl: string) => Promise<ServerNotificationsResponse>;
|
getServerNotifications: (etkeAdminUrl: string) => Promise<ServerNotificationsResponse>;
|
||||||
deleteServerNotifications: (etkeAdminUrl: string) => Promise<{ success: boolean }>;
|
deleteServerNotifications: (etkeAdminUrl: string) => Promise<{ success: boolean }>;
|
||||||
|
getServerCommands: (etkeAdminUrl: string) => Promise<ServerCommandsResponse>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const resourceMap = {
|
const resourceMap = {
|
||||||
@ -994,12 +1007,17 @@ const baseDataProvider: SynapseDataProvider = {
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getServerRunningProcess: async (runningProcessUrl: string): Promise<ServerProcessResponse> => {
|
getServerRunningProcess: async (etkeAdminUrl: string, burstCache: boolean = false): Promise<ServerProcessResponse> => {
|
||||||
const locked_at = "";
|
const locked_at = "";
|
||||||
const command = "";
|
const command = "";
|
||||||
|
|
||||||
|
let serverURL = `${etkeAdminUrl}/lock`;
|
||||||
|
if (burstCache) {
|
||||||
|
serverURL += `?time=${new Date().getTime()}`;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${runningProcessUrl}/lock`, {
|
const response = await fetch(serverURL, {
|
||||||
headers: {
|
headers: {
|
||||||
"Authorization": `Bearer ${localStorage.getItem("access_token")}`
|
"Authorization": `Bearer ${localStorage.getItem("access_token")}`
|
||||||
}
|
}
|
||||||
@ -1024,9 +1042,9 @@ const baseDataProvider: SynapseDataProvider = {
|
|||||||
|
|
||||||
return { locked_at, command };
|
return { locked_at, command };
|
||||||
},
|
},
|
||||||
getServerStatus: async (serverStatusUrl: string): Promise<ServerStatusResponse> => {
|
getServerStatus: async (etkeAdminUrl: string): Promise<ServerStatusResponse> => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${serverStatusUrl}/status`, {
|
const response = await fetch(`${etkeAdminUrl}/status`, {
|
||||||
headers: {
|
headers: {
|
||||||
"Authorization": `Bearer ${localStorage.getItem("access_token")}`
|
"Authorization": `Bearer ${localStorage.getItem("access_token")}`
|
||||||
}
|
}
|
||||||
@ -1101,6 +1119,66 @@ const baseDataProvider: SynapseDataProvider = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return { success: false };
|
return { success: false };
|
||||||
|
},
|
||||||
|
getServerCommands: async (serverCommandsUrl: string) => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${serverCommandsUrl}/commands`, {
|
||||||
|
headers: {
|
||||||
|
"Authorization": `Bearer ${localStorage.getItem("access_token")}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
console.error(`Error fetching server commands: ${response.status} ${response.statusText}`);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const status = response.status;
|
||||||
|
|
||||||
|
if (status === 200) {
|
||||||
|
const json = await response.json();
|
||||||
|
return json as ServerCommandsResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching server commands, error");
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
runServerCommand: async (serverCommandsUrl: string, command: string, additionalArgs: Record<string, any> = {}) => {
|
||||||
|
const endpoint_url = `${serverCommandsUrl}/commands`;
|
||||||
|
const body = {
|
||||||
|
command: command,
|
||||||
|
...additionalArgs
|
||||||
|
}
|
||||||
|
const response = await fetch(endpoint_url, {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Authorization": `Bearer ${localStorage.getItem("access_token")}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
console.error(`Error running server command: ${response.status} ${response.statusText}`);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const status = response.status;
|
||||||
|
|
||||||
|
if (status === 204) {
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -3,6 +3,11 @@ import EngineeringIcon from '@mui/icons-material/Engineering';
|
|||||||
import HelpCenterIcon from '@mui/icons-material/HelpCenter';
|
import HelpCenterIcon from '@mui/icons-material/HelpCenter';
|
||||||
import SupportAgentIcon from '@mui/icons-material/SupportAgent';
|
import SupportAgentIcon from '@mui/icons-material/SupportAgent';
|
||||||
import OpenInNewIcon from '@mui/icons-material/OpenInNew';
|
import OpenInNewIcon from '@mui/icons-material/OpenInNew';
|
||||||
|
import PieChartIcon from '@mui/icons-material/PieChart';
|
||||||
|
import UpgradeIcon from '@mui/icons-material/Upgrade';
|
||||||
|
import RouterIcon from '@mui/icons-material/Router';
|
||||||
|
import PriceCheckIcon from '@mui/icons-material/PriceCheck';
|
||||||
|
import RestartAltIcon from '@mui/icons-material/RestartAlt';
|
||||||
|
|
||||||
export const Icons = {
|
export const Icons = {
|
||||||
Announcement: AnnouncementIcon,
|
Announcement: AnnouncementIcon,
|
||||||
@ -10,6 +15,11 @@ export const Icons = {
|
|||||||
HelpCenter: HelpCenterIcon,
|
HelpCenter: HelpCenterIcon,
|
||||||
SupportAgent: SupportAgentIcon,
|
SupportAgent: SupportAgentIcon,
|
||||||
Default: OpenInNewIcon,
|
Default: OpenInNewIcon,
|
||||||
|
PieChart: PieChartIcon,
|
||||||
|
Upgrade: UpgradeIcon,
|
||||||
|
Router: RouterIcon,
|
||||||
|
PriceCheck: PriceCheckIcon,
|
||||||
|
RestartAlt: RestartAltIcon,
|
||||||
// Add more icons as needed
|
// Add more icons as needed
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user