machine view modes update

master
Tudor Stanciu 2023-03-24 00:43:28 +02:00
parent 6ad7abf3d1
commit 64684674ba
11 changed files with 250 additions and 126 deletions

View File

@ -26,6 +26,10 @@
"Settings": "Settings", "Settings": "Settings",
"About": "About" "About": "About"
}, },
"ViewModes": {
"Table": "Table",
"List": "List"
},
"Login": { "Login": {
"Username": "Username", "Username": "Username",
"Password": "Password", "Password": "Password",

View File

@ -17,6 +17,10 @@
"Settings": "Setări", "Settings": "Setări",
"About": "Despre" "About": "Despre"
}, },
"ViewModes": {
"Table": "Tabel",
"List": "Lista"
},
"Login": { "Login": {
"Username": "Utilizator", "Username": "Utilizator",
"Password": "Parolă", "Password": "Parolă",

View File

@ -10,6 +10,17 @@ import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
import withStyles from "@material-ui/core/styles/withStyles"; import withStyles from "@material-ui/core/styles/withStyles";
import MachineLog from "./common/MachineLog"; import MachineLog from "./common/MachineLog";
import { DataLabel } from "../../../components/common"; import { DataLabel } from "../../../components/common";
import { useTranslation } from "react-i18next";
import { useSensitiveInfo } from "../../../hooks";
import ActionsGroup from "./common/ActionsGroup";
import { makeStyles } from "@material-ui/core/styles";
const useStyles = makeStyles(() => ({
panel: {
justifyContent: "center",
alignItems: "center"
}
}));
const IconLeftAccordionSummary = withStyles({ const IconLeftAccordionSummary = withStyles({
expandIcon: { expandIcon: {
@ -17,7 +28,29 @@ const IconLeftAccordionSummary = withStyles({
} }
})(AccordionSummary); })(AccordionSummary);
const MachineAccordion = ({ machine, logs }) => { const GridCell = ({ label, value }) => {
const { mask } = useSensitiveInfo();
return (
<Grid item xs={12} md={6} lg={3}>
<DataLabel label={label} data={mask(value)} />
</Grid>
);
};
GridCell.propTypes = {
label: PropTypes.string.isRequired,
value: PropTypes.string.isRequired
};
const MachineAccordion = ({
machine,
actions,
logs,
addLog,
secondaryActionsMenuProps
}) => {
const { t } = useTranslation();
const classes = useStyles();
return ( return (
<Accordion> <Accordion>
<IconLeftAccordionSummary <IconLeftAccordionSummary
@ -27,11 +60,25 @@ const MachineAccordion = ({ machine, logs }) => {
id="additional-actions1-header" id="additional-actions1-header"
IconButtonProps={{ edge: "start" }} IconButtonProps={{ edge: "start" }}
> >
<Grid container> <Grid container className={classes.panel}>
<Grid item xs={12} md={6}> <Grid item xs={11}>
<DataLabel <Grid container>
label={"Full machine name"} <GridCell
data={machine.fullMachineName} label={t("Machine.FullName")}
value={machine.fullMachineName}
/>
<GridCell label={t("Machine.Name")} value={machine.machineName} />
<GridCell label={t("Machine.IP")} value={machine.iPv4Address} />
<GridCell label={t("Machine.MAC")} value={machine.macAddress} />
</Grid>
</Grid>
<Grid item xs={1} style={{ textAlign: "right" }}>
<ActionsGroup
className={classes.actions}
machine={machine}
actions={actions}
addLog={addLog}
secondaryActionsMenuProps={secondaryActionsMenuProps}
/> />
</Grid> </Grid>
</Grid> </Grid>
@ -44,8 +91,18 @@ const MachineAccordion = ({ machine, logs }) => {
}; };
MachineAccordion.propTypes = { MachineAccordion.propTypes = {
machine: PropTypes.object.isRequired, machine: PropTypes.shape({
logs: PropTypes.array.isRequired machineId: PropTypes.number.isRequired,
machineName: PropTypes.string.isRequired,
fullMachineName: PropTypes.string.isRequired,
macAddress: PropTypes.string.isRequired,
iPv4Address: PropTypes.string,
description: PropTypes.string
}).isRequired,
actions: PropTypes.array.isRequired,
logs: PropTypes.array.isRequired,
addLog: PropTypes.func.isRequired,
secondaryActionsMenuProps: PropTypes.object.isRequired
}; };
export default MachineAccordion; export default MachineAccordion;

View File

@ -1,18 +1,11 @@
import React, { useMemo } from "react"; import React from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { import { TableCell, TableRow, IconButton, Collapse } from "@material-ui/core";
TableCell,
TableRow,
IconButton,
Collapse,
Menu
} 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";
import MachineLog from "./common/MachineLog"; import MachineLog from "./common/MachineLog";
import WakeComponent from "./common/WakeComponent";
import ActionButton from "./common/ActionButton";
import { useSensitiveInfo } from "../../../hooks"; import { useSensitiveInfo } from "../../../hooks";
import ActionsGroup from "./common/ActionsGroup";
const useRowStyles = makeStyles({ const useRowStyles = makeStyles({
root: { root: {
@ -22,7 +15,7 @@ const useRowStyles = makeStyles({
} }
}); });
const Machine = ({ const MachineTableRow = ({
machine, machine,
actions, actions,
logs, logs,
@ -33,16 +26,6 @@ const Machine = ({
const classes = useRowStyles(); const classes = useRowStyles();
const { mask } = useSensitiveInfo(); const { mask } = useSensitiveInfo();
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}>
@ -62,29 +45,12 @@ const Machine = ({
<TableCell>{mask(machine.iPv4Address)}</TableCell> <TableCell>{mask(machine.iPv4Address)}</TableCell>
<TableCell>{mask(machine.macAddress)}</TableCell> <TableCell>{mask(machine.macAddress)}</TableCell>
<TableCell align="right"> <TableCell align="right">
<WakeComponent machine={machine} addLog={addLog} /> <ActionsGroup
{topActions.map(action => ( machine={machine}
<ActionButton actions={actions}
key={`machine-item-${machine.machineId}-${action.code}`} addLog={addLog}
action={action} secondaryActionsMenuProps={secondaryActionsMenuProps}
machine={machine} />
/>
))}
<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>
@ -98,7 +64,7 @@ const Machine = ({
); );
}; };
Machine.propTypes = { MachineTableRow.propTypes = {
machine: PropTypes.shape({ machine: PropTypes.shape({
machineId: PropTypes.number.isRequired, machineId: PropTypes.number.isRequired,
machineName: PropTypes.string.isRequired, machineName: PropTypes.string.isRequired,
@ -113,4 +79,4 @@ Machine.propTypes = {
secondaryActionsMenuProps: PropTypes.object.isRequired secondaryActionsMenuProps: PropTypes.object.isRequired
}; };
export default Machine; export default MachineTableRow;

View File

@ -4,7 +4,9 @@ import TableChartIcon from "@material-ui/icons/TableChart";
import ViewListIcon from "@material-ui/icons/ViewList"; import ViewListIcon from "@material-ui/icons/ViewList";
import ToggleButton from "@material-ui/lab/ToggleButton"; import ToggleButton from "@material-ui/lab/ToggleButton";
import ToggleButtonGroup from "@material-ui/lab/ToggleButtonGroup"; import ToggleButtonGroup from "@material-ui/lab/ToggleButtonGroup";
import { Tooltip } from "@material-ui/core";
import { useWindowSize } from "@flare/react-hooks"; import { useWindowSize } from "@flare/react-hooks";
import { useTranslation } from "react-i18next";
export const ViewModes = { export const ViewModes = {
TABLE: "table", TABLE: "table",
@ -12,41 +14,47 @@ export const ViewModes = {
}; };
const ViewModeSelection = ({ callback }) => { const ViewModeSelection = ({ callback }) => {
const [viewMode, setViewMode] = useState(ViewModes.TABLE); const [state, setState] = useState({
mode: ViewModes.TABLE,
manual: false
});
const { isMobile } = useWindowSize(); const { isMobile } = useWindowSize();
const { t } = useTranslation();
const handleViewModeSelection = useCallback( const handleViewModeSelection = useCallback(
(event, mode) => { (event, mode) => {
setViewMode(mode); setState({ mode, manual: true });
callback && callback(mode); callback && callback(mode);
}, },
[callback] [callback]
); );
useEffect( useEffect(() => {
() => if (state.manual === true) return;
handleViewModeSelection( const mode = isMobile ? ViewModes.ACCORDION : ViewModes.TABLE;
null, setState({ mode, manual: false });
isMobile ? ViewModes.ACCORDION : ViewModes.TABLE callback && callback(mode);
), }, [callback, isMobile, state.manual]);
[handleViewModeSelection, isMobile]
);
return ( return (
<ToggleButtonGroup <ToggleButtonGroup
size="small" size="small"
value={viewMode} value={state.mode}
exclusive exclusive
onChange={handleViewModeSelection} onChange={handleViewModeSelection}
> >
<ToggleButton value={ViewModes.TABLE} aria-label="table view mode"> <ToggleButton value={ViewModes.TABLE} aria-label="table view mode">
<TableChartIcon /> <Tooltip title={t("ViewModes.Table")}>
<TableChartIcon />
</Tooltip>
</ToggleButton> </ToggleButton>
<ToggleButton <ToggleButton
value={ViewModes.ACCORDION} value={ViewModes.ACCORDION}
aria-label="accordion view mode" aria-label="accordion view mode"
> >
<ViewListIcon /> <Tooltip title={t("ViewModes.List")}>
<ViewListIcon />
</Tooltip>
</ToggleButton> </ToggleButton>
</ToggleButtonGroup> </ToggleButtonGroup>
); );

View File

@ -4,6 +4,13 @@ import { IconButton, Tooltip } from "@material-ui/core";
const ActionButton = React.forwardRef((props, _ref) => { const ActionButton = React.forwardRef((props, _ref) => {
const { action, machine } = props; const { action, machine } = props;
const id = `machine-item-${machine.machineId}-${action.code}`;
const handleActionClick = event => {
if (action.system) action.effect();
else action.effect(machine);
event.stopPropagation();
};
return ( return (
<Tooltip <Tooltip
id={`machine-item-${machine.machineId}-${action.code}-tooltip`} id={`machine-item-${machine.machineId}-${action.code}-tooltip`}
@ -11,9 +18,10 @@ const ActionButton = React.forwardRef((props, _ref) => {
> >
<span> <span>
<IconButton <IconButton
id={`machine-item-${machine.machineId}-${action.code}`} id={id}
size={"small"} size={"small"}
onClick={action.system ? action.effect : action.effect(machine)} onFocus={event => event.stopPropagation()}
onClick={handleActionClick}
> >
<action.icon /> <action.icon />
</IconButton> </IconButton>

View File

@ -0,0 +1,66 @@
import React, { useMemo } from "react";
import PropTypes from "prop-types";
import WakeComponent from "./WakeComponent";
import ActionButton from "./ActionButton";
import { Menu } from "@material-ui/core";
const ActionsGroup = ({
machine,
actions,
addLog,
secondaryActionsMenuProps
}) => {
const topActions = useMemo(
() => actions.filter(a => a.top === true),
[actions]
);
const secondaryActions = useMemo(
() => actions.filter(a => a.top === false),
[actions]
);
return (
<>
<WakeComponent machine={machine} addLog={addLog} />
{topActions.map(action => (
<ActionButton
key={`machine-item-${machine.machineId}-${action.code}`}
action={action}
machine={machine}
/>
))}
<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>
</>
);
};
ActionsGroup.propTypes = {
machine: PropTypes.shape({
machineId: PropTypes.number.isRequired,
machineName: PropTypes.string.isRequired,
fullMachineName: PropTypes.string.isRequired,
macAddress: PropTypes.string.isRequired,
iPv4Address: PropTypes.string,
description: PropTypes.string
}).isRequired,
actions: PropTypes.array.isRequired,
addLog: PropTypes.func.isRequired,
secondaryActionsMenuProps: PropTypes.object.isRequired
};
export default ActionsGroup;

View File

@ -13,7 +13,7 @@ const MachineLog = ({ logs }) => {
); );
return ( return (
<Box margin={1}> <Box width="100%">
<div style={{ height: 200 }}> <div style={{ height: 200 }}>
<ScrollFollow <ScrollFollow
startFollowing={true} startFollowing={true}

View File

@ -80,6 +80,11 @@ const WakeComponent = ({ machine, addLog }) => {
useEffect(pingInLoop, [trigger, pingInLoop]); useEffect(pingInLoop, [trigger, pingInLoop]);
const handleWakeClick = event => {
wakeMachine();
event.stopPropagation();
};
return ( return (
<Tooltip title={t(state.on ? "Machine.PoweredOn" : "Machine.Actions.Wake")}> <Tooltip title={t(state.on ? "Machine.PoweredOn" : "Machine.Actions.Wake")}>
<span> <span>
@ -87,8 +92,9 @@ const WakeComponent = ({ machine, addLog }) => {
id={`machine-${machine.machineId}-wake`} id={`machine-${machine.machineId}-wake`}
size={"small"} size={"small"}
disabled={state.on} disabled={state.on}
onClick={wakeMachine} onClick={handleWakeClick}
style={state.on ? { color: "#33cc33" } : {}} style={state.on ? { color: "#33cc33" } : {}}
onFocus={event => event.stopPropagation()}
> >
<PowerSettingsNew /> <PowerSettingsNew />
</IconButton> </IconButton>

View File

@ -16,11 +16,16 @@ import { FileCopyOutlined } from "@material-ui/icons";
import EmailIcon from "@material-ui/icons/Email"; import EmailIcon from "@material-ui/icons/Email";
import { useToast } from "../../../../hooks"; import { useToast } from "../../../../hooks";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { makeStyles } from "@material-ui/core/styles";
import styles from "../styles";
const useStyles = makeStyles(styles);
const UserProfileCardContent = ({ userData }) => { const UserProfileCardContent = ({ userData }) => {
const { email, profilePictureUrl } = userData; const { email, profilePictureUrl } = userData;
const { t } = useTranslation(); const { t } = useTranslation();
const { info } = useToast(); const { info } = useToast();
const classes = useStyles();
const handleCopyToClipboard = url => () => { const handleCopyToClipboard = url => () => {
navigator.clipboard.writeText(url); navigator.clipboard.writeText(url);
@ -35,73 +40,66 @@ const UserProfileCardContent = ({ userData }) => {
const userName = `${userData.firstName} ${userData.lastName}`; const userName = `${userData.firstName} ${userData.lastName}`;
return ( return (
<Grid container spacing={2}> <div className={classes.panel}>
<Grid item xs={12} sm={4} lg={2}> <UserProfilePicture userData={userData} />
<UserProfilePicture userData={userData} /> <Grid container spacing={2}>
</Grid> <Grid item xs={12} sm={6}>
<Grid item xs={12} sm={8} lg={10}> <List>
<Grid container spacing={2}> <ListItem dense>
<Grid item xs={12} sm={6}> <ListItemIcon>
<List> <BusinessCenterIcon />
</ListItemIcon>
<ListItemText
primary={
<Tooltip title={t("User.Profile.OpenPortfolio")}>
<Link href="https://lab.code-rove.com/tsp/" target="_blank">
{userName}
</Link>
</Tooltip>
}
/>
</ListItem>
<ListItem dense>
<ListItemIcon>
<EmailIcon />
</ListItemIcon>
<ListItemText
primary={
<Tooltip title={t("Generic.SendEmail")}>
<Link href="#" onClick={handleEmailSending}>
{email}
</Link>
</Tooltip>
}
/>
</ListItem>
{profilePictureUrl && (
<ListItem dense> <ListItem dense>
<ListItemIcon> <ListItemIcon>
<BusinessCenterIcon /> <Tooltip title={t("Generic.Copy")}>
<IconButton
size="small"
onClick={handleCopyToClipboard(profilePictureUrl)}
>
<FileCopyOutlined />
</IconButton>
</Tooltip>
</ListItemIcon> </ListItemIcon>
<ListItemText <ListItemText
primary={ primary={
<Tooltip title={t("User.Profile.OpenPortfolio")}> <Tooltip title={t("Generic.OpenInNewTab")}>
<Link <Link href={profilePictureUrl} target="_blank">
href="https://lab.code-rove.com/tsp/" {profilePictureUrl}
target="_blank"
>
{userName}
</Link> </Link>
</Tooltip> </Tooltip>
} }
/> />
</ListItem> </ListItem>
<ListItem dense> )}
<ListItemIcon> </List>
<EmailIcon />
</ListItemIcon>
<ListItemText
primary={
<Tooltip title={t("Generic.SendEmail")}>
<Link href="#" onClick={handleEmailSending}>
{email}
</Link>
</Tooltip>
}
/>
</ListItem>
{profilePictureUrl && (
<ListItem dense>
<ListItemIcon>
<Tooltip title={t("Generic.Copy")}>
<IconButton
size="small"
onClick={handleCopyToClipboard(profilePictureUrl)}
>
<FileCopyOutlined />
</IconButton>
</Tooltip>
</ListItemIcon>
<ListItemText
primary={
<Tooltip title={t("Generic.OpenInNewTab")}>
<Link href={profilePictureUrl} target="_blank">
{profilePictureUrl}
</Link>
</Tooltip>
}
/>
</ListItem>
)}
</List>
</Grid>
</Grid> </Grid>
</Grid> </Grid>
</Grid> </div>
); );
}; };

View File

@ -1,5 +1,12 @@
const style = theme => { const style = theme => {
return { return {
panel: {
display: "flex",
flexDirection: "row",
"@media (max-width: 600px)": {
flexDirection: "column" // change direction for small screens
}
},
profilePicture: { profilePicture: {
margin: "auto", margin: "auto",
display: "block", display: "block",