Shutdown and restart actions

master
Tudor Stanciu 2022-01-18 17:18:30 +02:00
parent 5536cddf1b
commit 8eb5f0caa0
6 changed files with 211 additions and 36 deletions

View File

@ -13,3 +13,8 @@ Log-ul va fi popup iar continutul lui poate fi ultima componenta de aici https:/
https://medium.com/@tacomanator/environments-with-create-react-app-7b645312c09d https://medium.com/@tacomanator/environments-with-create-react-app-7b645312c09d
https://create-react-app.dev/docs/adding-custom-environment-variables/ https://create-react-app.dev/docs/adding-custom-environment-variables/
https://stackoverflow.com/questions/55690143/what-is-the-difference-between-env-local-and-env-development-local https://stackoverflow.com/questions/55690143/what-is-the-difference-between-env-local-and-env-development-local
REACT v4:
https://v4.mui.com/getting-started/installation/
https://v4.mui.com/components/material-icons/

View File

@ -36,7 +36,11 @@
"PoweredOn": "Powered on", "PoweredOn": "Powered on",
"Actions": { "Actions": {
"Wake": "Wake", "Wake": "Wake",
"Ping": "Ping" "Ping": "Ping",
"More": "More",
"Shutdown": "Shutdown",
"Restart": "Restart",
"Advanced": "Advanced"
} }
} }
} }

View File

@ -27,7 +27,11 @@
"PoweredOn": "Pornit", "PoweredOn": "Pornit",
"Actions": { "Actions": {
"Wake": "Pornește", "Wake": "Pornește",
"Ping": "Ping" "Ping": "Ping",
"More": "Mai mult",
"Shutdown": "Oprește",
"Restart": "Repornește",
"Advanced": "Avansat"
} }
} }
} }

View File

@ -25,7 +25,10 @@ const useApi = () => {
error(message); error(message);
}; };
const defaultOptions = { onCompleted: () => {}, onError: handleError }; const defaultOptions = {
onCompleted: () => {},
onError: handleError
};
const call = async (request, options) => { const call = async (request, options) => {
const internalOptions = { ...defaultOptions, ...options }; const internalOptions = { ...defaultOptions, ...options };
@ -63,7 +66,39 @@ const useApi = () => {
return promise; return promise;
}; };
return { readMachines, wakeMachine, pingMachine }; const shutdownMachine = (
machineId,
delay,
force,
options = defaultOptions
) => {
const promise = call(
() => post(`${powerActionsRoute}/shutdown`, { machineId, delay, force }),
options
);
return promise;
};
const restartMachine = (
machineId,
delay,
force,
options = defaultOptions
) => {
const promise = call(
() => post(`${powerActionsRoute}/restart`, { machineId, delay, force }),
options
);
return promise;
};
return {
readMachines,
wakeMachine,
pingMachine,
shutdownMachine,
restartMachine
};
}; };
export default useApi; export default useApi;

View File

@ -1,11 +1,12 @@
import React from "react"; import React, { useMemo } from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { import {
TableCell, TableCell,
TableRow, TableRow,
IconButton, IconButton,
Collapse, Collapse,
Tooltip Tooltip,
Menu
} from "@material-ui/core"; } from "@material-ui/core";
import { KeyboardArrowDown, KeyboardArrowUp } from "@material-ui/icons"; import { KeyboardArrowDown, KeyboardArrowUp } from "@material-ui/icons";
import { makeStyles } from "@material-ui/core/styles"; import { makeStyles } from "@material-ui/core/styles";
@ -20,10 +21,46 @@ const useRowStyles = makeStyles({
} }
}); });
const Machine = ({ machine, actions, logs, addLog }) => { const ActionButton = React.forwardRef((props, _ref) => {
const { action, machine } = props;
return (
<Tooltip
id={`machine-item-${machine.machineId}-${action.code}-tooltip`}
title={action.tooltip}
>
<span>
<IconButton
id={`machine-item-${machine.machineId}-${action.code}`}
size={"small"}
onClick={action.system ? action.effect : action.effect(machine)}
>
<action.icon />
</IconButton>
</span>
</Tooltip>
);
});
const Machine = ({
machine,
actions,
logs,
addLog,
secondaryActionsMenuProps
}) => {
const [open, setOpen] = React.useState(false); const [open, setOpen] = React.useState(false);
const classes = useRowStyles(); const classes = useRowStyles();
const topActions = useMemo(
() => actions.filter(a => a.top === true),
[actions]
);
const secondaryActions = useMemo(
() => actions.filter(a => a.top === false),
[actions]
);
return ( return (
<React.Fragment> <React.Fragment>
<TableRow className={classes.root}> <TableRow className={classes.root}>
@ -44,22 +81,28 @@ const Machine = ({ machine, actions, logs, addLog }) => {
<TableCell>{machine.macAddress}</TableCell> <TableCell>{machine.macAddress}</TableCell>
<TableCell align="right"> <TableCell align="right">
<WakeComponent machine={machine} addLog={addLog} /> <WakeComponent machine={machine} addLog={addLog} />
{actions.map(action => ( {topActions.map(action => (
<Tooltip <ActionButton
title={action.tooltip} key={`machine-item-${machine.machineId}-${action.code}`}
key={`machine-item-${machine.machineId}-${action.code}-tooltip`} action={action}
> machine={machine}
<span> />
<IconButton
id={`machine-item-${machine.machineId}-${action.code}`}
size={"small"}
onClick={action.effect(machine)}
>
<action.icon />
</IconButton>
</span>
</Tooltip>
))} ))}
<Menu
id="secondary-actions-menu"
anchorEl={secondaryActionsMenuProps.anchor}
keepMounted
open={Boolean(secondaryActionsMenuProps.anchor)}
onClose={secondaryActionsMenuProps.onCloseSecondaryActions}
>
{secondaryActions.map(action => (
<ActionButton
key={`machine-item-${machine.machineId}-${action.code}`}
action={action}
machine={machine}
/>
))}
</Menu>
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
@ -84,7 +127,8 @@ Machine.propTypes = {
}).isRequired, }).isRequired,
actions: PropTypes.array.isRequired, actions: PropTypes.array.isRequired,
logs: PropTypes.array.isRequired, logs: PropTypes.array.isRequired,
addLog: PropTypes.func.isRequired addLog: PropTypes.func.isRequired,
secondaryActionsMenuProps: PropTypes.object.isRequired
}; };
export default Machine; export default Machine;

View File

@ -2,12 +2,21 @@ import React, { useState, useCallback } from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import Machine from "./Machine"; import Machine from "./Machine";
import { useToast } from "../../../hooks"; import { useToast } from "../../../hooks";
import { LastPage } from "@material-ui/icons"; import {
LastPage,
MoreHoriz,
RotateLeft,
Launch,
Stop
} from "@material-ui/icons";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import useApi from "../../../api"; import useApi from "../../../api";
const MachineContainer = ({ machine }) => { const MachineContainer = ({ machine }) => {
const [logs, setLogs] = useState([]); const [logs, setLogs] = useState([]);
const [secondaryActionsAnchor, setSecondaryActionsAnchor] =
React.useState(null);
const { success, error } = useToast(); const { success, error } = useToast();
const { t } = useTranslation(); const { t } = useTranslation();
@ -20,20 +29,58 @@ const MachineContainer = ({ machine }) => {
[setLogs] [setLogs]
); );
const manageActionResponse = useCallback(
response => {
addLog(`Success: ${response.success}. Status: ${response.status}`);
if (response.success) {
success(response.status);
} else {
error(response.status);
}
},
[error, success, addLog]
);
const pingMachine = useCallback( const pingMachine = useCallback(
machine => async () => { machine => async () => {
await api.pingMachine(machine.machineId, { await api.pingMachine(machine.machineId, {
onCompleted: result => { onCompleted: manageActionResponse
addLog(`Success: ${result.success}. Status: ${result.status}`);
if (result.success) {
success(result.status);
} else {
error(result.status);
}
}
}); });
}, },
[error, success, addLog, api] [manageActionResponse, api]
);
const handleOpenSecondaryActions = event => {
setSecondaryActionsAnchor(event.currentTarget);
};
const handleCloseSecondaryActions = () => {
setSecondaryActionsAnchor(null);
};
const secondaryActionsMenuProps = {
anchor: secondaryActionsAnchor,
onCloseSecondaryActions: handleCloseSecondaryActions
};
const shutdownMachine = useCallback(
machine => async () => {
await api.shutdownMachine(machine.machineId, 0, false, {
onCompleted: manageActionResponse
});
handleCloseSecondaryActions();
},
[manageActionResponse, api]
);
const restartMachine = useCallback(
machine => async () => {
await api.restartMachine(machine.machineId, 0, false, {
onCompleted: manageActionResponse
});
handleCloseSecondaryActions();
},
[manageActionResponse, api]
); );
const actions = [ const actions = [
@ -41,12 +88,48 @@ const MachineContainer = ({ machine }) => {
code: "ping", code: "ping",
effect: pingMachine, effect: pingMachine,
icon: LastPage, icon: LastPage,
tooltip: t("Machine.Actions.Ping") tooltip: t("Machine.Actions.Ping"),
top: true
},
{
code: "more",
effect: handleOpenSecondaryActions,
icon: MoreHoriz,
tooltip: t("Machine.Actions.More"),
top: true,
system: true
},
{
code: "shutdown",
effect: shutdownMachine,
icon: Stop,
tooltip: t("Machine.Actions.Shutdown"),
top: false
},
{
code: "restart",
effect: restartMachine,
icon: RotateLeft,
tooltip: t("Machine.Actions.Restart"),
top: false
},
{
code: "advanced",
effect: () => {},
icon: Launch,
tooltip: t("Machine.Actions.Advanced"),
top: false
} }
]; ];
return ( return (
<Machine machine={machine} actions={actions} logs={logs} addLog={addLog} /> <Machine
machine={machine}
actions={actions}
logs={logs}
addLog={addLog}
secondaryActionsMenuProps={secondaryActionsMenuProps}
/>
); );
}; };