mirror of
https://dev.azure.com/tstanciu94/NetworkResurrector/_git/NetworkResurrector_Frontend
synced 2023-05-06 14:40:17 +03:00
Compare commits
No commits in common. "52cd8f53f551c0f8d5f114b97695218299ab8801" and "7e1a64c27bf5514274fbeda826d6d49c7951ff4f" have entirely different histories.
52cd8f53f5
...
7e1a64c27b
8
.env
8
.env
@ -1,8 +1,8 @@
|
||||
#REACT_APP_TUITIO_URL=http://localhost:5063
|
||||
REACT_APP_TUITIO_URL=https://lab.code-rove.com/tuitio
|
||||
#REACT_APP_TUITIO_URL=http://localhost:5063/identity/authenticate?UserName={username}&Password={password}
|
||||
|
||||
REACT_APP_NETWORK_RESURRECTOR_API_URL=http://localhost:5064
|
||||
#REACT_APP_NETWORK_RESURRECTOR_API_URL=https://lab.code-rove.com/network-resurrector-api
|
||||
REACT_APP_TUITIO_URL=https://lab.code-rove.com/tuitio
|
||||
#REACT_APP_NETWORK_RESURRECTOR_API_URL=http://localhost:5064
|
||||
REACT_APP_NETWORK_RESURRECTOR_API_URL=https://lab.code-rove.com/network-resurrector-api
|
||||
|
||||
#600000 milliseconds = 10 minutes
|
||||
REACT_APP_MACHINE_PING_INTERVAL=600000
|
||||
|
@ -27,9 +27,6 @@ ENV AUTHOR="Tudor Stanciu"
|
||||
ARG APP_VERSION=0.0.0
|
||||
ENV APP_VERSION=${APP_VERSION}
|
||||
|
||||
ARG APP_DATE="-"
|
||||
ENV APP_DATE=${APP_DATE}
|
||||
|
||||
#set workdir to root
|
||||
WORKDIR /
|
||||
|
||||
|
14
package-lock.json
generated
14
package-lock.json
generated
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "network-resurrector-frontend",
|
||||
"version": "1.2.3",
|
||||
"version": "1.2.2",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@ -1980,18 +1980,6 @@
|
||||
"@babel/runtime": "^7.4.4"
|
||||
}
|
||||
},
|
||||
"@material-ui/lab": {
|
||||
"version": "4.0.0-alpha.61",
|
||||
"resolved": "https://registry.npmjs.org/@material-ui/lab/-/lab-4.0.0-alpha.61.tgz",
|
||||
"integrity": "sha512-rSzm+XKiNUjKegj8bzt5+pygZeckNLOr+IjykH8sYdVk7dE9y2ZuUSofiMV2bJk3qU+JHwexmw+q0RyNZB9ugg==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.4.4",
|
||||
"@material-ui/utils": "^4.11.3",
|
||||
"clsx": "^1.0.4",
|
||||
"prop-types": "^15.7.2",
|
||||
"react-is": "^16.8.0 || ^17.0.0"
|
||||
}
|
||||
},
|
||||
"@material-ui/styles": {
|
||||
"version": "4.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.11.5.tgz",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "network-resurrector-frontend",
|
||||
"version": "1.2.3",
|
||||
"version": "1.2.2",
|
||||
"description": "Frontend component of Network resurrector system",
|
||||
"author": {
|
||||
"name": "Tudor Stanciu",
|
||||
@ -17,7 +17,6 @@
|
||||
"@flare/tuitio-client-react": "^1.1.1",
|
||||
"@material-ui/core": "^4.11.2",
|
||||
"@material-ui/icons": "^4.11.2",
|
||||
"@material-ui/lab": "^4.0.0-alpha.61",
|
||||
"axios": "^1.3.4",
|
||||
"i18next": "^19.4.4",
|
||||
"i18next-browser-languagedetector": "^4.1.1",
|
||||
|
@ -12,39 +12,22 @@
|
||||
"English": "English",
|
||||
"Romanian": "Romanian"
|
||||
},
|
||||
"Generic": {
|
||||
"Copy": "Copy",
|
||||
"OpenInNewTab": "Open in new tab",
|
||||
"CopiedToClipboard": "Copied to clipboard",
|
||||
"SendEmail": "Send email"
|
||||
},
|
||||
"Menu": {
|
||||
"Dashboard": "Dashboard",
|
||||
"Machines": "Machines",
|
||||
"System": "System",
|
||||
"Administration": "Administration",
|
||||
"Settings": "Settings",
|
||||
"About": "About"
|
||||
},
|
||||
"ViewModes": {
|
||||
"Table": "Table",
|
||||
"List": "List"
|
||||
"Trash": "Trash",
|
||||
"Settings": "Settings"
|
||||
},
|
||||
"Login": {
|
||||
"Username": "Username",
|
||||
"Password": "Password",
|
||||
"Label": "Login",
|
||||
"IncorrectCredentials": "Incorrect credentials."
|
||||
},
|
||||
"User": {
|
||||
"Profile": {
|
||||
"Label": "Profile",
|
||||
"Hello": "Hi, {{userName}}",
|
||||
"Description": "{{userName}}, authenticated on {{loginDate}}",
|
||||
"OpenPortfolio": "Open portfolio"
|
||||
},
|
||||
"Settings": "Settings",
|
||||
"Logout": "Logout"
|
||||
"ChangeUser": "Change user",
|
||||
"UserChanged": "User changed",
|
||||
"Logout": "Logout",
|
||||
"IncorrectCredentials": "Incorrect credentials.",
|
||||
"Hello": "Hi, {{username}}",
|
||||
"AuthenticationDate": "Authentication date"
|
||||
},
|
||||
"Machine": {
|
||||
"FullName": "Full machine name",
|
||||
@ -60,52 +43,5 @@
|
||||
"Restart": "Restart",
|
||||
"Advanced": "Advanced"
|
||||
}
|
||||
},
|
||||
"System": {
|
||||
"Navigation": {
|
||||
"MainServices": "Main services",
|
||||
"Agents": "Agents"
|
||||
}
|
||||
},
|
||||
"Settings": {
|
||||
"Navigation": {
|
||||
"Appearance": "Appearance",
|
||||
"Notifications": "Notifications"
|
||||
}
|
||||
},
|
||||
"About": {
|
||||
"Navigation": {
|
||||
"System": "System",
|
||||
"ReleaseNotes": "Release notes",
|
||||
"Timeline": "Timeline"
|
||||
},
|
||||
"ReleaseNotes": {
|
||||
"Version": "Version",
|
||||
"Date": "Date"
|
||||
},
|
||||
"System": {
|
||||
"Description": {
|
||||
"Title": "Network resurrector system",
|
||||
"FirstPhrase": "Everything must be able to be managed remotely. Even the powered off servers. That's how Network resurrector appeared, the tool I wrote specifically to be able to wake up my machines that I don't need to be powered on all the time.",
|
||||
"SecondPhrase": "Network Resurrector is a system that comprises of five essential services which allow for the execution of its core functionality. To enable various additional features, such as the notification mechanism, supplementary components may be added to the system as an option.",
|
||||
"Frontend": "Frontend: The frontend component is a web application written in React JS that has the role of providing the user with a friendly visual interface through which to interact with the system.",
|
||||
"Api": "API: The API component is a .NET 6 REST API that has the role of mediating the exchange of data and commands between the frontend component and the database or server component.",
|
||||
"Server": "Server: The server component is a .NET 6 service specialized in executing 'WakeOnLAN', 'Ping' and 'Shutdown' actions for the machines in its network.",
|
||||
"Agent": "Agent: The agent is a .NET 6 service specialized in executing 'Shutdown', 'Restart', 'Sleep', 'Logout' and 'Lock' actions on the host machine on which it is installed. Each action can be executed at the time of launch or with a certain delay. If an action is requested with a delay and later the user changes his mind, he can cancel the action by executing the separate 'Cancel' type action.",
|
||||
"Tuitio": "Tuitio: Tuitio is my personal identity server. It manages user authentication within the application and authorizes requests made by it. Further information about Tuitio can be found on its dedicated page."
|
||||
},
|
||||
"Services": {
|
||||
"Frontend": "Frontend",
|
||||
"Api": "API",
|
||||
"Server": "Server",
|
||||
"Tuitio": "Tuitio"
|
||||
},
|
||||
"Version": {
|
||||
"Server": "Server: {{version}}",
|
||||
"Api": "API: {{version}}",
|
||||
"Frontend": "UI: {{version}}",
|
||||
"LastReleaseDate": "Last update date: {{date}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,39 +3,22 @@
|
||||
"English": "Engleză",
|
||||
"Romanian": "Română"
|
||||
},
|
||||
"Generic": {
|
||||
"Copy": "Copiază",
|
||||
"OpenInNewTab": "Deschide într-un tab nou",
|
||||
"CopiedToClipboard": "Copiat în clipboard",
|
||||
"SendEmail": "Trimite email"
|
||||
},
|
||||
"Menu": {
|
||||
"Dashboard": "Bord",
|
||||
"Machines": "Mașini",
|
||||
"System": "Sistem",
|
||||
"Administration": "Administrare",
|
||||
"Settings": "Setări",
|
||||
"About": "Despre"
|
||||
},
|
||||
"ViewModes": {
|
||||
"Table": "Tabel",
|
||||
"List": "Lista"
|
||||
"Trash": "Gunoi",
|
||||
"Settings": "Setări"
|
||||
},
|
||||
"Login": {
|
||||
"Username": "Utilizator",
|
||||
"Password": "Parolă",
|
||||
"Label": "Autentificare",
|
||||
"IncorrectCredentials": "Credențiale incorecte."
|
||||
},
|
||||
"User": {
|
||||
"Profile": {
|
||||
"Label": "Profil",
|
||||
"Hello": "Salut, {{userName}}",
|
||||
"Description": "{{userName}}, autentificat pe {{loginDate}}",
|
||||
"OpenPortfolio": "Deschide portofoliu"
|
||||
},
|
||||
"Settings": "Setări",
|
||||
"Logout": "Deconectare"
|
||||
"ChangeUser": "Schimbă utilizatorul",
|
||||
"UserChanged": "Utilizator schimbat",
|
||||
"Logout": "Deconectare",
|
||||
"IncorrectCredentials": "Credențiale incorecte.",
|
||||
"Hello": "Salut, {{username}}",
|
||||
"AuthenticationDate": "Momentul autentificării"
|
||||
},
|
||||
"Machine": {
|
||||
"FullName": "Nume intreg masina",
|
||||
@ -51,52 +34,5 @@
|
||||
"Restart": "Repornește",
|
||||
"Advanced": "Avansat"
|
||||
}
|
||||
},
|
||||
"System": {
|
||||
"Navigation": {
|
||||
"MainServices": "Servicii principale",
|
||||
"Agents": "Agenți"
|
||||
}
|
||||
},
|
||||
"Settings": {
|
||||
"Navigation": {
|
||||
"Appearance": "Aspect",
|
||||
"Notifications": "Notificări"
|
||||
}
|
||||
},
|
||||
"About": {
|
||||
"Navigation": {
|
||||
"System": "Sistem",
|
||||
"ReleaseNotes": "Note de lansare",
|
||||
"Timeline": "Cronologie"
|
||||
},
|
||||
"ReleaseNotes": {
|
||||
"Version": "Versiune",
|
||||
"Date": "Dată"
|
||||
},
|
||||
"System": {
|
||||
"Description": {
|
||||
"Title": "Network resurrector system",
|
||||
"FirstPhrase": "Totul trebuie să poată fi gestionat de la distanță. Chiar și serverele oprite. Așa a apărut Network resurrector, instrumentul pe care l-am scris special pentru a-mi putea porni mașinile de care nu am nevoie să fie pornite tot timpul.",
|
||||
"SecondPhrase": "Network Resurrector este un sistem care cuprinde cinci servicii esențiale care permit executarea funcționalității sale de bază. Pentru a activa diverse funcții suplimentare, cum ar fi mecanismul de notificare, componente suplimentare pot fi adăugate la sistem ca opțiune.",
|
||||
"Frontend": "Frontend: Componenta frontend este o aplicație web scrisă în React JS care are rolul de a oferi utilizatorului o interfață vizuală prietenoasă prin care să interacționeze cu sistemul.",
|
||||
"Api": "API: Componenta API este un .NET 6 REST API care are rolul de a media schimbul de date și comenzi între componenta frontend și baza de date sau componenta server.",
|
||||
"Server": "Server: Componenta server este un serviciu .NET 6 specializat în executarea acțiunilor 'WakeOnLAN', 'Ping' și 'Shutdown' pentru mașinile din rețeaua sa.",
|
||||
"Agent": "Agent: Agentul este un serviciu .NET 6 specializat în executarea acțiunilor 'Shutdown', 'Restart', 'Sleep', 'Logout' și 'Lock' pe mașina gazdă pe care este instalat. Fiecare acțiune poate fi executată în momentul lansării sau cu o anumită întârziere. Dacă o acțiune este solicitată cu întârziere și ulterior utilizatorul se răzgândește, el poate anula acțiunea executând comanda separată de tip 'Cancel'.",
|
||||
"Tuitio": "Tuitio: Tuitio este serverul meu de identitate personală. Gestionează autentificarea utilizatorilor în cadrul aplicației și autorizează solicitările făcute de aceasta. Mai multe informații despre Tuitio pot fi găsite pe pagina sa dedicată."
|
||||
},
|
||||
"Services": {
|
||||
"Frontend": "Frontend",
|
||||
"Api": "API",
|
||||
"Server": "Server",
|
||||
"Tuitio": "Tuitio"
|
||||
},
|
||||
"Version": {
|
||||
"Server": "Server: {{version}}",
|
||||
"Api": "API: {{version}}",
|
||||
"Frontend": "UI: {{version}}",
|
||||
"LastReleaseDate": "Data ultimei actualizări: {{date}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ import { useToast } from "../hooks";
|
||||
import { get, post } from "../utils/axios";
|
||||
|
||||
const networkRoute = `${process.env.REACT_APP_NETWORK_RESURRECTOR_API_URL}/network`;
|
||||
const systemRoute = `${process.env.REACT_APP_NETWORK_RESURRECTOR_API_URL}/system`;
|
||||
const powerActionsRoute = `${process.env.REACT_APP_NETWORK_RESURRECTOR_API_URL}/resurrector`;
|
||||
|
||||
const useApi = () => {
|
||||
@ -43,22 +42,6 @@ const useApi = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const getSystemVersion = (options = defaultOptions) => {
|
||||
const releaseNotesPromise = call(
|
||||
() => get(`${systemRoute}/version`),
|
||||
options
|
||||
);
|
||||
return releaseNotesPromise;
|
||||
};
|
||||
|
||||
const readReleaseNotes = (options = defaultOptions) => {
|
||||
const releaseNotesPromise = call(
|
||||
() => get(`${systemRoute}/release-notes`),
|
||||
options
|
||||
);
|
||||
return releaseNotesPromise;
|
||||
};
|
||||
|
||||
const readMachines = (options = defaultOptions) => {
|
||||
const machinesPromise = call(
|
||||
() => get(`${networkRoute}/machines`),
|
||||
@ -110,8 +93,6 @@ const useApi = () => {
|
||||
};
|
||||
|
||||
return {
|
||||
getSystemVersion,
|
||||
readReleaseNotes,
|
||||
readMachines,
|
||||
wakeMachine,
|
||||
pingMachine,
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 89 KiB |
@ -1,42 +0,0 @@
|
||||
import React, { useMemo } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
panel: {
|
||||
display: "flex"
|
||||
},
|
||||
label: {
|
||||
marginRight: "4px"
|
||||
},
|
||||
data: {
|
||||
fontWeight: theme.typography.fontWeightMedium
|
||||
}
|
||||
}));
|
||||
|
||||
const DataLabel = ({ label, data }) => {
|
||||
const classes = useStyles();
|
||||
const lbl = useMemo(
|
||||
() => (label.endsWith(":") ? label : `${label}:`),
|
||||
[label]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={classes.panel}>
|
||||
<Typography variant="body2" className={classes.label}>
|
||||
{lbl}
|
||||
</Typography>
|
||||
<Typography variant="body2" className={classes.data}>
|
||||
{data}
|
||||
</Typography>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
DataLabel.propTypes = {
|
||||
label: PropTypes.string.isRequired,
|
||||
data: PropTypes.string
|
||||
};
|
||||
|
||||
export default DataLabel;
|
@ -1,45 +0,0 @@
|
||||
import React, { useState } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import ToggleButton from "@material-ui/lab/ToggleButton";
|
||||
import ToggleButtonGroup from "@material-ui/lab/ToggleButtonGroup";
|
||||
import { Tooltip } from "@material-ui/core";
|
||||
|
||||
const NavigationButtons = ({ tabs, onTabChange }) => {
|
||||
const [selected, setSelected] = useState(tabs[0].code);
|
||||
|
||||
const handleTabSelection = (_event, tabCode) => {
|
||||
setSelected(tabCode);
|
||||
onTabChange && onTabChange(tabCode);
|
||||
};
|
||||
|
||||
return (
|
||||
<ToggleButtonGroup
|
||||
size="small"
|
||||
value={selected}
|
||||
exclusive
|
||||
onChange={handleTabSelection}
|
||||
>
|
||||
{tabs.map(tab => (
|
||||
<ToggleButton
|
||||
key={tab.code}
|
||||
value={tab.code}
|
||||
aria-label="navigation buttons"
|
||||
disabled={selected === tab.code}
|
||||
>
|
||||
<Tooltip title={tab.tooltip}>
|
||||
<tab.icon color="primary" />
|
||||
</Tooltip>
|
||||
</ToggleButton>
|
||||
))}
|
||||
</ToggleButtonGroup>
|
||||
);
|
||||
};
|
||||
|
||||
NavigationButtons.propTypes = {
|
||||
tabs: PropTypes.arrayOf(
|
||||
PropTypes.shape({ code: PropTypes.string.isRequired })
|
||||
).isRequired,
|
||||
onTabChange: PropTypes.func
|
||||
};
|
||||
|
||||
export default NavigationButtons;
|
@ -7,38 +7,26 @@ const useStyles = makeStyles(theme => ({
|
||||
box: {
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
marginBottom: theme.spacing(1),
|
||||
marginBottom: theme.spacing(2),
|
||||
marginTop: theme.spacing(0)
|
||||
},
|
||||
title: {
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
flexDirection: "column",
|
||||
minHeight: "40px"
|
||||
},
|
||||
titleText: { textTransform: "uppercase" }
|
||||
title: { textTransform: "uppercase" }
|
||||
}));
|
||||
|
||||
const PageTitle = ({ text, toolBar, navigation }) => {
|
||||
const PageTitle = ({ text }) => {
|
||||
const classes = useStyles();
|
||||
|
||||
return (
|
||||
<div className={classes.box}>
|
||||
{navigation && navigation}
|
||||
<div className={classes.title}>
|
||||
<Typography className={classes.titleText} variant="h3" size="sm">
|
||||
{text}
|
||||
</Typography>
|
||||
</div>
|
||||
{toolBar && toolBar}
|
||||
<Typography className={classes.title} variant="h3" size="sm">
|
||||
{text}
|
||||
</Typography>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
PageTitle.propTypes = {
|
||||
text: PropTypes.string.isRequired,
|
||||
toolBar: PropTypes.node,
|
||||
navigation: PropTypes.node
|
||||
text: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
export default PageTitle;
|
||||
|
@ -1,3 +0,0 @@
|
||||
import DataLabel from "./DataLabel";
|
||||
|
||||
export { DataLabel };
|
75
src/components/common/stepper/ApplicationStepper.js
Normal file
75
src/components/common/stepper/ApplicationStepper.js
Normal file
@ -0,0 +1,75 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import steps from "./steps";
|
||||
import {
|
||||
Card,
|
||||
Stepper,
|
||||
Step,
|
||||
StepButton,
|
||||
StepLabel,
|
||||
makeStyles
|
||||
} from "@material-ui/core";
|
||||
import { useLocation, useHistory } from "react-router-dom";
|
||||
import CustomStepConnector from "./CustomStepConnector";
|
||||
import StepIcon from "./StepIcon";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const styles = () => ({
|
||||
stepperCard: {
|
||||
margin: "15px"
|
||||
},
|
||||
stepper: {
|
||||
padding: "10px"
|
||||
}
|
||||
});
|
||||
|
||||
const useStyles = makeStyles(styles);
|
||||
|
||||
const ApplicationStepper = () => {
|
||||
const firstStep = steps[0];
|
||||
const [activeStep, setActiveStep] = useState(firstStep);
|
||||
const classes = useStyles();
|
||||
const location = useLocation();
|
||||
const history = useHistory();
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
const step = steps.find(z => z.route === location.pathname);
|
||||
if (step) {
|
||||
setActiveStep(step);
|
||||
}
|
||||
}, [location.pathname]);
|
||||
|
||||
const handleStepClick = step => {
|
||||
if (!step) return;
|
||||
setActiveStep(step);
|
||||
if (location.pathname === step.route) return;
|
||||
history.push(step.route);
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className={classes.stepperCard}>
|
||||
<Stepper
|
||||
className={classes.stepper}
|
||||
activeStep={activeStep.id}
|
||||
orientation="horizontal"
|
||||
connector={<CustomStepConnector />}
|
||||
>
|
||||
{steps.map(step => (
|
||||
<Step key={step.id}>
|
||||
<StepButton
|
||||
id={"appStepper_" + step.title}
|
||||
disabled={step.disabled}
|
||||
onClick={() => handleStepClick(step)}
|
||||
>
|
||||
<StepLabel StepIconComponent={StepIcon}>
|
||||
{t(step.title)}
|
||||
</StepLabel>
|
||||
</StepButton>
|
||||
</Step>
|
||||
))}
|
||||
</Stepper>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default ApplicationStepper;
|
28
src/components/common/stepper/CustomStepConnector.js
Normal file
28
src/components/common/stepper/CustomStepConnector.js
Normal file
@ -0,0 +1,28 @@
|
||||
import { withStyles } from "@material-ui/core/styles";
|
||||
import StepConnector from "@material-ui/core/StepConnector";
|
||||
|
||||
const CustomStepConnector = withStyles({
|
||||
alternativeLabel: {
|
||||
top: 22
|
||||
},
|
||||
active: {
|
||||
"& $line": {
|
||||
backgroundImage:
|
||||
"linear-gradient( 95deg,rgb(242,113,33) 0%,rgb(233,64,87) 50%,rgb(138,35,135) 100%)"
|
||||
}
|
||||
},
|
||||
completed: {
|
||||
"& $line": {
|
||||
backgroundImage:
|
||||
"linear-gradient( 95deg,rgb(242,113,33) 0%,rgb(233,64,87) 50%,rgb(138,35,135) 100%)"
|
||||
}
|
||||
},
|
||||
line: {
|
||||
height: 3,
|
||||
border: 0,
|
||||
backgroundColor: "#eaeaf0",
|
||||
borderRadius: 1
|
||||
}
|
||||
})(StepConnector);
|
||||
|
||||
export default CustomStepConnector;
|
66
src/components/common/stepper/StepIcon.js
Normal file
66
src/components/common/stepper/StepIcon.js
Normal file
@ -0,0 +1,66 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import clsx from "clsx";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import steps from "./steps";
|
||||
|
||||
const useStepIconStyles = makeStyles({
|
||||
root: {
|
||||
backgroundColor: "#ccc",
|
||||
zIndex: 1,
|
||||
color: "#fff",
|
||||
width: 50,
|
||||
height: 50,
|
||||
display: "flex",
|
||||
borderRadius: "50%",
|
||||
justifyContent: "center",
|
||||
alignItems: "center"
|
||||
},
|
||||
active: {
|
||||
backgroundImage:
|
||||
"linear-gradient( 136deg, rgb(242,113,33) 0%, rgb(233,64,87) 50%, rgb(138,35,135) 100%)",
|
||||
boxShadow: "0 4px 10px 0 rgba(0,0,0,.25)"
|
||||
},
|
||||
completed: {
|
||||
backgroundImage:
|
||||
"linear-gradient( 136deg, rgb(242,113,33) 0%, rgb(233,64,87) 50%, rgb(138,35,135) 100%)"
|
||||
}
|
||||
});
|
||||
|
||||
function StepIcon(props) {
|
||||
const classes = useStepIconStyles();
|
||||
const { active, completed } = props;
|
||||
|
||||
const getIcon = icon => {
|
||||
const step = steps.find(z => z.id === icon - 1);
|
||||
return step.icon;
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(classes.root, {
|
||||
[classes.active]: active,
|
||||
[classes.completed]: completed
|
||||
})}
|
||||
>
|
||||
{getIcon(props.icon)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
StepIcon.propTypes = {
|
||||
/**
|
||||
* Whether this step is active.
|
||||
*/
|
||||
active: PropTypes.bool,
|
||||
/**
|
||||
* Mark the step as completed. Is passed to child components.
|
||||
*/
|
||||
completed: PropTypes.bool,
|
||||
/**
|
||||
* The label displayed in the step icon.
|
||||
*/
|
||||
icon: PropTypes.node
|
||||
};
|
||||
|
||||
export default StepIcon;
|
27
src/components/common/stepper/steps.js
Normal file
27
src/components/common/stepper/steps.js
Normal file
@ -0,0 +1,27 @@
|
||||
import { Router, VpnKey, Settings } from "@material-ui/icons";
|
||||
|
||||
const steps = [
|
||||
{
|
||||
id: 0,
|
||||
title: "Steps.Login",
|
||||
route: "/",
|
||||
disabled: false,
|
||||
icon: <VpnKey />
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
title: "Steps.Network",
|
||||
route: "/machines",
|
||||
disabled: false,
|
||||
icon: <Router />
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: "Steps.Settings",
|
||||
route: "/settings",
|
||||
disabled: false,
|
||||
icon: <Settings />
|
||||
}
|
||||
];
|
||||
|
||||
export default steps;
|
@ -1,22 +1,18 @@
|
||||
import React from "react";
|
||||
import { Route, Switch } from "react-router-dom";
|
||||
import PageNotFound from "./PageNotFound";
|
||||
import LoginContainer from "../../features/login/components/LoginContainer";
|
||||
import NetworkContainer from "../../features/network/components/NetworkContainer";
|
||||
import SystemContainer from "../../features/system/SystemContainer";
|
||||
import SettingsContainer from "../../features/settings/SettingsContainer";
|
||||
import SettingsContainer from "../../features/settings/components/SettingsContainer";
|
||||
import DashboardContainer from "../../features/dashboard/components/DashboardContainer";
|
||||
import UserProfileContainer from "../../features/user/profile/components/UserProfileContainer";
|
||||
import AboutContainer from "../../features/about/AboutContainer";
|
||||
|
||||
const AppRoutes = () => {
|
||||
return (
|
||||
<Switch>
|
||||
<Route exact path="/dashboard" component={DashboardContainer} />
|
||||
<Route exact path="/user-profile" component={UserProfileContainer} />
|
||||
<Route exact path="/user-profile" component={LoginContainer} />
|
||||
<Route exact path="/machines" component={NetworkContainer} />
|
||||
<Route exact path="/system" component={SystemContainer} />
|
||||
<Route exact path="/settings" component={SettingsContainer} />
|
||||
<Route exact path="/about" component={AboutContainer} />
|
||||
<Route component={PageNotFound} />
|
||||
</Switch>
|
||||
);
|
||||
|
@ -1,29 +1,13 @@
|
||||
import React, { useState } from "react";
|
||||
import {
|
||||
IconButton,
|
||||
Menu,
|
||||
MenuItem,
|
||||
Typography,
|
||||
ListItemIcon
|
||||
} from "@material-ui/core";
|
||||
import { IconButton, Menu, MenuItem } from "@material-ui/core";
|
||||
import AccountCircle from "@material-ui/icons/AccountCircle";
|
||||
import ExitToAppIcon from "@material-ui/icons/ExitToApp";
|
||||
import AccountBoxIcon from "@material-ui/icons/AccountBox";
|
||||
import SettingsIcon from "@material-ui/icons/Settings";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import { useTuitioClient } from "@flare/tuitio-client-react";
|
||||
import { useToast } from "../../hooks";
|
||||
import styles from "./styles";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const useStyles = makeStyles(styles);
|
||||
|
||||
const ProfileButton = () => {
|
||||
const history = useHistory();
|
||||
const { error } = useToast();
|
||||
const classes = useStyles();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { logout } = useTuitioClient({
|
||||
onLogoutFailed: errorMessage => error(errorMessage),
|
||||
@ -73,28 +57,9 @@ const ProfileButton = () => {
|
||||
handleClose();
|
||||
}}
|
||||
>
|
||||
<ListItemIcon className={classes.menuItemIcon}>
|
||||
<AccountBoxIcon fontSize="small" />
|
||||
</ListItemIcon>
|
||||
<Typography variant="inherit">{t("User.Profile.Label")}</Typography>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
history.push("/settings");
|
||||
handleClose();
|
||||
}}
|
||||
>
|
||||
<ListItemIcon className={classes.menuItemIcon}>
|
||||
<SettingsIcon fontSize="small" />
|
||||
</ListItemIcon>
|
||||
<Typography variant="inherit">{t("User.Settings")}</Typography>
|
||||
</MenuItem>
|
||||
<MenuItem onClick={logout}>
|
||||
<ListItemIcon className={classes.menuItemIcon}>
|
||||
<ExitToAppIcon fontSize="small" />
|
||||
</ListItemIcon>
|
||||
<Typography variant="inherit">{t("User.Logout")}</Typography>
|
||||
Profile
|
||||
</MenuItem>
|
||||
<MenuItem onClick={logout}>Logout</MenuItem>
|
||||
</Menu>
|
||||
</div>
|
||||
);
|
||||
|
@ -13,12 +13,10 @@ import {
|
||||
import ChevronLeftIcon from "@material-ui/icons/ChevronLeft";
|
||||
import ChevronRightIcon from "@material-ui/icons/ChevronRight";
|
||||
import ListItem from "@material-ui/core/ListItem";
|
||||
import BuildIcon from "@material-ui/icons/Build";
|
||||
import DeleteIcon from "@material-ui/icons/Delete";
|
||||
import DnsIcon from "@material-ui/icons/Dns";
|
||||
import DeviceHubIcon from "@material-ui/icons/DeviceHub";
|
||||
import SettingsIcon from "@material-ui/icons/Settings";
|
||||
import DashboardIcon from "@material-ui/icons/Dashboard";
|
||||
import FeaturedPlayListIcon from "@material-ui/icons/FeaturedPlayList";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import styles from "./styles";
|
||||
@ -76,24 +74,14 @@ const Sidebar = ({ open, handleDrawerClose }) => {
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={t("Menu.Machines")} />
|
||||
</ListItem>
|
||||
<ListItem button key="system" onClick={() => history.push("/system")}>
|
||||
<ListItemIcon>
|
||||
<DeviceHubIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={t("Menu.System")} />
|
||||
</ListItem>
|
||||
</List>
|
||||
<Divider />
|
||||
<List>
|
||||
<ListItem
|
||||
button
|
||||
key="administration"
|
||||
onClick={() => history.push("/administration")}
|
||||
>
|
||||
<ListItem button key="trash" onClick={() => history.push("/trash")}>
|
||||
<ListItemIcon>
|
||||
<BuildIcon />
|
||||
<DeleteIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={t("Menu.Administration")} />
|
||||
<ListItemText primary={t("Menu.Trash")} />
|
||||
</ListItem>
|
||||
<ListItem
|
||||
button
|
||||
@ -106,15 +94,6 @@ const Sidebar = ({ open, handleDrawerClose }) => {
|
||||
<ListItemText primary={t("Menu.Settings")} />
|
||||
</ListItem>
|
||||
</List>
|
||||
<Divider />
|
||||
<List>
|
||||
<ListItem button key="about" onClick={() => history.push("/about")}>
|
||||
<ListItemIcon>
|
||||
<FeaturedPlayListIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={t("Menu.About")} />
|
||||
</ListItem>
|
||||
</List>
|
||||
</Drawer>
|
||||
);
|
||||
};
|
||||
|
@ -62,9 +62,6 @@ const styles = theme => ({
|
||||
content: {
|
||||
flexGrow: 1,
|
||||
padding: theme.spacing(2)
|
||||
},
|
||||
menuItemIcon: {
|
||||
minWidth: "26px"
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -1,58 +0,0 @@
|
||||
import React, { useState, useMemo } from "react";
|
||||
import PageTitle from "../../components/common/PageTitle";
|
||||
import BubbleChartIcon from "@material-ui/icons/BubbleChart";
|
||||
import NotesIcon from "@material-ui/icons/Notes";
|
||||
import TimelineIcon from "@material-ui/icons/Timeline";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import AboutSystemContainer from "./system/AboutSystemContainer";
|
||||
import ReleaseNotesContainer from "./releaseNotes/ReleaseNotesContainer";
|
||||
import NavigationButtons from "../../components/common/NavigationButtons";
|
||||
|
||||
const NavigationTabs = {
|
||||
SYSTEM: "About.Navigation.System",
|
||||
RELEASE_NOTES: "About.Navigation.ReleaseNotes",
|
||||
TIMELINE: "About.Navigation.Timeline"
|
||||
};
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
code: NavigationTabs.SYSTEM,
|
||||
icon: BubbleChartIcon
|
||||
},
|
||||
{
|
||||
code: NavigationTabs.RELEASE_NOTES,
|
||||
icon: NotesIcon
|
||||
},
|
||||
{
|
||||
code: NavigationTabs.TIMELINE,
|
||||
icon: TimelineIcon
|
||||
}
|
||||
];
|
||||
|
||||
const AboutContainer = () => {
|
||||
const [tab, setTab] = useState(NavigationTabs.SYSTEM);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const navigationTabs = useMemo(
|
||||
() => tabs.map(z => ({ ...z, tooltip: t(z.code) })),
|
||||
[t]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageTitle
|
||||
text={t(tab)}
|
||||
navigation={
|
||||
<NavigationButtons tabs={navigationTabs} onTabChange={setTab} />
|
||||
}
|
||||
/>
|
||||
{tab === NavigationTabs.SYSTEM && <AboutSystemContainer />}
|
||||
{tab === NavigationTabs.RELEASE_NOTES && <ReleaseNotesContainer />}
|
||||
{tab === NavigationTabs.TIMELINE && (
|
||||
<ReleaseNotesContainer view="timeline" />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default AboutContainer;
|
7
src/features/about/components/AboutContainer.js
Normal file
7
src/features/about/components/AboutContainer.js
Normal file
@ -0,0 +1,7 @@
|
||||
import React from "react";
|
||||
|
||||
const AboutContainer = () => {
|
||||
return <div>TEST</div>;
|
||||
};
|
||||
|
||||
export default AboutContainer;
|
@ -1,27 +0,0 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
|
||||
const ReleaseNote = ({ releaseNote }) => {
|
||||
return (
|
||||
<div>
|
||||
{releaseNote.notes.map(note => {
|
||||
return (
|
||||
<Typography
|
||||
key={releaseNote.notes.indexOf(note)}
|
||||
variant="body2"
|
||||
gutterBottom
|
||||
>
|
||||
{note}
|
||||
</Typography>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
ReleaseNote.propTypes = {
|
||||
releaseNote: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
export default ReleaseNote;
|
@ -1,37 +0,0 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { Grid, Typography } from "@material-ui/core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const ReleaseNoteSummary = ({ releaseNote, collapsed }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Grid container>
|
||||
<Grid item xs={6} sm={2} md={2}>
|
||||
<Typography variant={collapsed ? "subtitle2" : "h6"}>
|
||||
{`${t("About.ReleaseNotes.Version")}: ${releaseNote.version}`}
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={6} sm={2} md={collapsed ? 2 : 4}>
|
||||
<Typography variant={collapsed ? "subtitle2" : "h6"}>
|
||||
{`${t("About.ReleaseNotes.Date")}: ${t("DATE_FORMAT", {
|
||||
date: { value: releaseNote.date, format: "DD-MM-YYYY HH:mm" }
|
||||
})}`}
|
||||
</Typography>
|
||||
</Grid>
|
||||
{collapsed && (
|
||||
<Grid item xs={12} sm={8} md={8}>
|
||||
<Typography variant="body2">{releaseNote.notes[0]}</Typography>
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
ReleaseNoteSummary.propTypes = {
|
||||
releaseNote: PropTypes.object.isRequired,
|
||||
collapsed: PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
export default ReleaseNoteSummary;
|
@ -1,40 +0,0 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import useApi from "../../../api";
|
||||
import ReleaseNotesList from "./ReleaseNotesList";
|
||||
import TimelineComponent from "../timeline/TimelineComponent";
|
||||
|
||||
const sort = releases =>
|
||||
releases.sort((a, b) => new Date(b.date) - new Date(a.date));
|
||||
|
||||
const ReleaseNotesContainer = ({ view }) => {
|
||||
const [state, setState] = useState({ data: [], loaded: false });
|
||||
|
||||
const api = useApi();
|
||||
|
||||
useEffect(() => {
|
||||
if (state.loaded) return;
|
||||
api.readReleaseNotes({
|
||||
onCompleted: data => {
|
||||
setState({ data, loaded: true });
|
||||
}
|
||||
});
|
||||
}, [api, state.loaded]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{state.loaded &&
|
||||
(view === "timeline" ? (
|
||||
<TimelineComponent releases={sort(state.data)} />
|
||||
) : (
|
||||
<ReleaseNotesList releases={sort(state.data)} />
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
ReleaseNotesContainer.propTypes = {
|
||||
view: PropTypes.string
|
||||
};
|
||||
|
||||
export default ReleaseNotesContainer;
|
@ -1,64 +0,0 @@
|
||||
import React, { useState } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import {
|
||||
Accordion,
|
||||
AccordionSummary,
|
||||
AccordionDetails
|
||||
} from "@material-ui/core";
|
||||
import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
|
||||
import ReleaseNoteSummary from "./ReleaseNoteSummary";
|
||||
import ReleaseNote from "./ReleaseNote";
|
||||
|
||||
const ReleaseNotesList = ({ releases }) => {
|
||||
const [flags, setFlags] = useState({});
|
||||
|
||||
const handleToggle = key => (_, expanded) => {
|
||||
setFlags(prev => ({
|
||||
...prev,
|
||||
[key]: expanded
|
||||
}));
|
||||
};
|
||||
|
||||
const isCollapsed = key => {
|
||||
const expanded = flags[key];
|
||||
const collapsed = !expanded || expanded === false;
|
||||
return collapsed;
|
||||
};
|
||||
|
||||
console.log(
|
||||
"sortedReleases",
|
||||
JSON.stringify(releases.map(z => ({ version: z.version, date: z.date })))
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{releases.map(release => {
|
||||
return (
|
||||
<Accordion
|
||||
key={release.version}
|
||||
onChange={handleToggle(release.version)}
|
||||
>
|
||||
<AccordionSummary
|
||||
expandIcon={<ExpandMoreIcon />}
|
||||
id={`panel-${release.version}-header`}
|
||||
>
|
||||
<ReleaseNoteSummary
|
||||
releaseNote={release}
|
||||
collapsed={isCollapsed(release.version)}
|
||||
/>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<ReleaseNote releaseNote={release} />
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
ReleaseNotesList.propTypes = {
|
||||
releases: PropTypes.array.isRequired
|
||||
};
|
||||
|
||||
export default ReleaseNotesList;
|
@ -1,103 +0,0 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import Card from "@material-ui/core/Card";
|
||||
import CardActions from "@material-ui/core/CardActions";
|
||||
import CardContent from "@material-ui/core/CardContent";
|
||||
import Button from "@material-ui/core/Button";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import OpenInNewIcon from "@material-ui/icons/OpenInNew";
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
bullet: {
|
||||
display: "inline-block",
|
||||
margin: "0 4px",
|
||||
transform: "scale(1.5)"
|
||||
},
|
||||
service: {
|
||||
marginTop: theme.spacing(1)
|
||||
}
|
||||
}));
|
||||
|
||||
const buttons = [
|
||||
{
|
||||
code: "About.System.Services.Frontend",
|
||||
url: "https://lab.code-rove.com/gitea/tudor.stanciu/network-resurrector-frontend"
|
||||
},
|
||||
{
|
||||
code: "About.System.Services.Api",
|
||||
url: "https://lab.code-rove.com/gitea/tudor.stanciu/network-resurrector"
|
||||
},
|
||||
{
|
||||
code: "About.System.Services.Server",
|
||||
url: "https://lab.code-rove.com/gitea/tudor.stanciu/network-resurrector"
|
||||
},
|
||||
{
|
||||
code: "About.System.Services.Tuitio",
|
||||
url: "https://lab.code-rove.com/gitea/tudor.stanciu/tuitio"
|
||||
}
|
||||
];
|
||||
|
||||
const AboutSystemComponent = ({ handleOpenInNewTab }) => {
|
||||
const classes = useStyles();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const bullet = <span className={classes.bullet}>•</span>;
|
||||
|
||||
return (
|
||||
<Card variant="outlined">
|
||||
<CardContent>
|
||||
<Typography variant="h5" gutterBottom>
|
||||
{t("About.System.Description.Title")}
|
||||
</Typography>
|
||||
<Typography color="textSecondary">
|
||||
{t("About.System.Description.FirstPhrase")}
|
||||
</Typography>
|
||||
<Typography color="textSecondary">
|
||||
{t("About.System.Description.SecondPhrase")}
|
||||
</Typography>
|
||||
|
||||
<Typography className={classes.service} color="textSecondary">
|
||||
{bullet}
|
||||
{t("About.System.Description.Frontend")}
|
||||
</Typography>
|
||||
<Typography className={classes.service} color="textSecondary">
|
||||
{bullet}
|
||||
{t("About.System.Description.Api")}
|
||||
</Typography>
|
||||
<Typography className={classes.service} color="textSecondary">
|
||||
{bullet}
|
||||
{t("About.System.Description.Server")}
|
||||
</Typography>
|
||||
<Typography className={classes.service} color="textSecondary">
|
||||
{bullet}
|
||||
{t("About.System.Description.Agent")}
|
||||
</Typography>
|
||||
<Typography className={classes.service} color="textSecondary">
|
||||
{bullet}
|
||||
{t("About.System.Description.Tuitio")}
|
||||
</Typography>
|
||||
</CardContent>
|
||||
<CardActions>
|
||||
{buttons.map(button => (
|
||||
<Button
|
||||
key={button.code}
|
||||
size="small"
|
||||
color="primary"
|
||||
startIcon={<OpenInNewIcon />}
|
||||
onClick={handleOpenInNewTab(button.url)}
|
||||
>
|
||||
{t(button.code)}
|
||||
</Button>
|
||||
))}
|
||||
</CardActions>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
AboutSystemComponent.propTypes = {
|
||||
handleOpenInNewTab: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default AboutSystemComponent;
|
@ -1,35 +0,0 @@
|
||||
import React from "react";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import AboutSystemComponent from "./AboutSystemComponent";
|
||||
import SystemVersionContainer from "./SystemVersionContainer";
|
||||
|
||||
const useStyles = makeStyles(theme => {
|
||||
return {
|
||||
page: {
|
||||
display: "flex",
|
||||
flexDirection: "column"
|
||||
},
|
||||
element: {
|
||||
marginTop: theme.spacing(1)
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
const AboutSystemContainer = () => {
|
||||
const classes = useStyles();
|
||||
|
||||
const handleOpenInNewTab = url => event => {
|
||||
window.open(url, "_blank");
|
||||
event.preventDefault();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={classes.page}>
|
||||
<AboutSystemComponent handleOpenInNewTab={handleOpenInNewTab} />
|
||||
<div className={classes.element}>
|
||||
<SystemVersionContainer />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default AboutSystemContainer;
|
@ -1,154 +0,0 @@
|
||||
import React, { useMemo, useEffect, useState } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import {
|
||||
List,
|
||||
ListItem,
|
||||
ListItemText,
|
||||
ListItemAvatar
|
||||
} from "@material-ui/core";
|
||||
import Avatar from "@material-ui/core/Avatar";
|
||||
import WebAssetIcon from "@material-ui/icons/WebAsset";
|
||||
import DeveloperBoardIcon from "@material-ui/icons/DeveloperBoard";
|
||||
import SettingsInputSvideoIcon from "@material-ui/icons/SettingsInputSvideo";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import packageData from "../../../../package.json";
|
||||
import Paper from "@material-ui/core/Paper";
|
||||
|
||||
const useStyles = makeStyles(theme => {
|
||||
return {
|
||||
horizontally: {
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
padding: 0
|
||||
},
|
||||
vertical: {
|
||||
width: "100%"
|
||||
},
|
||||
value: {
|
||||
fontSize: "0.9rem",
|
||||
fontWeight: theme.typography.fontWeightMedium
|
||||
},
|
||||
versionAvatar: {
|
||||
backgroundColor: theme.palette.secondary.main
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
const SystemVersionComponent = ({ data }) => {
|
||||
const classes = useStyles();
|
||||
const [listClass, setListClass] = useState(classes.horizontally);
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
const mediaQuery = window.matchMedia("(max-width: 800px)");
|
||||
|
||||
function handleMatches(event) {
|
||||
const cssClass = event.matches ? classes.vertical : classes.horizontally;
|
||||
setListClass(cssClass);
|
||||
}
|
||||
|
||||
handleMatches(mediaQuery);
|
||||
mediaQuery.addListener(handleMatches);
|
||||
|
||||
return () => {
|
||||
mediaQuery.removeListener(handleMatches);
|
||||
};
|
||||
}, [classes.horizontally, classes.vertical]);
|
||||
|
||||
const lastReleaseDate = useMemo(() => {
|
||||
const format = "DD-MM-YYYY HH:mm:ss";
|
||||
const server = t("DATE_FORMAT", {
|
||||
date: {
|
||||
value: data.server.lastReleaseDate,
|
||||
format
|
||||
}
|
||||
});
|
||||
|
||||
const api = t("DATE_FORMAT", {
|
||||
date: {
|
||||
value: data.api.lastReleaseDate,
|
||||
format
|
||||
}
|
||||
});
|
||||
|
||||
const frontend = t("DATE_FORMAT", {
|
||||
date: {
|
||||
value: process.env.APP_DATE ?? new Date(),
|
||||
format
|
||||
}
|
||||
});
|
||||
|
||||
return { server, api, frontend };
|
||||
}, [data, t]);
|
||||
|
||||
return (
|
||||
<Paper variant="outlined">
|
||||
<List className={listClass}>
|
||||
<ListItem>
|
||||
<ListItemAvatar>
|
||||
<Avatar className={classes.versionAvatar}>
|
||||
<DeveloperBoardIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText
|
||||
primary={
|
||||
<span className={classes.value}>
|
||||
{t("About.System.Version.Server", {
|
||||
version: data.server.version
|
||||
})}
|
||||
</span>
|
||||
}
|
||||
secondary={t("About.System.Version.LastReleaseDate", {
|
||||
date: lastReleaseDate.server
|
||||
})}
|
||||
/>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<ListItemAvatar>
|
||||
<Avatar className={classes.versionAvatar}>
|
||||
<SettingsInputSvideoIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText
|
||||
primary={
|
||||
<span className={classes.value}>
|
||||
{t("About.System.Version.Api", {
|
||||
version: data.api.version
|
||||
})}
|
||||
</span>
|
||||
}
|
||||
secondary={t("About.System.Version.LastReleaseDate", {
|
||||
date: lastReleaseDate.api
|
||||
})}
|
||||
/>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<ListItemAvatar>
|
||||
<Avatar className={classes.versionAvatar}>
|
||||
<WebAssetIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText
|
||||
primary={
|
||||
<span className={classes.value}>
|
||||
{t("About.System.Version.Frontend", {
|
||||
version: process.env.APP_VERSION ?? packageData.version
|
||||
})}
|
||||
</span>
|
||||
}
|
||||
secondary={t("About.System.Version.LastReleaseDate", {
|
||||
date: lastReleaseDate.frontend
|
||||
})}
|
||||
/>
|
||||
</ListItem>
|
||||
</List>
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
|
||||
SystemVersionComponent.propTypes = {
|
||||
data: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
export default SystemVersionComponent;
|
@ -1,22 +0,0 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import SystemVersionComponent from "./SystemVersionComponent";
|
||||
import useApi from "../../../api";
|
||||
|
||||
const SystemVersionContainer = () => {
|
||||
const [state, setState] = useState({ data: {}, loaded: false });
|
||||
|
||||
const api = useApi();
|
||||
|
||||
useEffect(() => {
|
||||
if (state.loaded) return;
|
||||
api.getSystemVersion({
|
||||
onCompleted: data => {
|
||||
setState({ data, loaded: true });
|
||||
}
|
||||
});
|
||||
}, [api, state.loaded]);
|
||||
|
||||
return <>{state.loaded && <SystemVersionComponent data={state.data} />}</>;
|
||||
};
|
||||
|
||||
export default SystemVersionContainer;
|
@ -1,117 +0,0 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import Timeline from "@material-ui/lab/Timeline";
|
||||
import TimelineItem from "@material-ui/lab/TimelineItem";
|
||||
import TimelineSeparator from "@material-ui/lab/TimelineSeparator";
|
||||
import TimelineConnector from "@material-ui/lab/TimelineConnector";
|
||||
import TimelineContent from "@material-ui/lab/TimelineContent";
|
||||
import TimelineOppositeContent from "@material-ui/lab/TimelineOppositeContent";
|
||||
import TimelineDot from "@material-ui/lab/TimelineDot";
|
||||
import Paper from "@material-ui/core/Paper";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { getRandomElement } from "../../../utils";
|
||||
import {
|
||||
Announcement,
|
||||
AmpStories,
|
||||
Apps,
|
||||
BugReport,
|
||||
DeviceHub,
|
||||
Equalizer,
|
||||
FilterTiltShift,
|
||||
Grain,
|
||||
Layers,
|
||||
LocalOffer,
|
||||
Memory,
|
||||
NetworkCheck,
|
||||
OfflineBolt,
|
||||
Star,
|
||||
Whatshot,
|
||||
Widgets
|
||||
} from "@material-ui/icons";
|
||||
|
||||
const timelineIcons = [
|
||||
Announcement,
|
||||
AmpStories,
|
||||
Apps,
|
||||
BugReport,
|
||||
DeviceHub,
|
||||
Equalizer,
|
||||
FilterTiltShift,
|
||||
Grain,
|
||||
Layers,
|
||||
LocalOffer,
|
||||
Memory,
|
||||
NetworkCheck,
|
||||
OfflineBolt,
|
||||
Star,
|
||||
Whatshot,
|
||||
Widgets
|
||||
];
|
||||
|
||||
const timelineDotVariants = [
|
||||
{ color: "primary", variant: "outlined" },
|
||||
{ color: "secondary", variant: "outlined" }
|
||||
];
|
||||
|
||||
const useStyles = makeStyles(() => ({
|
||||
paper: {
|
||||
padding: "6px 16px"
|
||||
}
|
||||
}));
|
||||
|
||||
const TimelineComponent = ({ releases }) => {
|
||||
const classes = useStyles();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const _releases = releases.map((release, index) => {
|
||||
const isLast = index === releases.length - 1;
|
||||
const icon = getRandomElement(timelineIcons);
|
||||
const dot = getRandomElement(timelineDotVariants);
|
||||
return { ...release, isLast, icon, dot };
|
||||
});
|
||||
|
||||
return (
|
||||
<Timeline align="alternate">
|
||||
{_releases.map(release => (
|
||||
<TimelineItem key={release.version}>
|
||||
<TimelineOppositeContent>
|
||||
<Typography variant="body2" color="textSecondary">
|
||||
{t("DATE_FORMAT", {
|
||||
date: { value: release.date, format: "DD-MM-YYYY HH:mm" }
|
||||
})}
|
||||
</Typography>
|
||||
</TimelineOppositeContent>
|
||||
<TimelineSeparator>
|
||||
<TimelineDot
|
||||
color={release.dot.color}
|
||||
variant={release.dot.variant}
|
||||
>
|
||||
<release.icon />
|
||||
</TimelineDot>
|
||||
{!release.isLast && <TimelineConnector />}
|
||||
</TimelineSeparator>
|
||||
<TimelineContent>
|
||||
<Paper elevation={3} className={classes.paper}>
|
||||
<Typography variant="h6" component="h1">
|
||||
{release.notes[0]}
|
||||
</Typography>
|
||||
{release.notes.slice(1).map((note, index) => (
|
||||
<Typography key={index} variant="body2">
|
||||
{note}
|
||||
</Typography>
|
||||
))}
|
||||
</Paper>
|
||||
</TimelineContent>
|
||||
</TimelineItem>
|
||||
))}
|
||||
</Timeline>
|
||||
);
|
||||
};
|
||||
|
||||
TimelineComponent.propTypes = {
|
||||
releases: PropTypes.array.isRequired
|
||||
};
|
||||
|
||||
export default TimelineComponent;
|
111
src/features/login/components/LoggedInComponent.js
Normal file
111
src/features/login/components/LoggedInComponent.js
Normal file
@ -0,0 +1,111 @@
|
||||
import React, { useState, useMemo } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import {
|
||||
Avatar,
|
||||
Collapse,
|
||||
Tooltip,
|
||||
Divider,
|
||||
Typography,
|
||||
Card,
|
||||
CardContent,
|
||||
CardActions,
|
||||
CardHeader,
|
||||
IconButton
|
||||
} from "@material-ui/core";
|
||||
import { AccountBox, RotateLeft, ExitToApp } from "@material-ui/icons";
|
||||
import LoginComponent from "./LoginComponent";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useToast } from "../../../hooks";
|
||||
import styles from "../styles";
|
||||
import { useTuitioUser } from "@flare/tuitio-client-react";
|
||||
|
||||
const useStyles = makeStyles(styles);
|
||||
|
||||
const LoggedInComponent = ({ credentials, onChange, onLogin, onLogout }) => {
|
||||
const classes = useStyles();
|
||||
const { t } = useTranslation();
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const { info } = useToast();
|
||||
const { userName } = useTuitioUser();
|
||||
|
||||
const handleExpandLogin = () => {
|
||||
setExpanded(!expanded);
|
||||
};
|
||||
|
||||
const loginDate = useMemo(() => {
|
||||
const valueForDisplay = t("LONG_DATE", { date: new Date() });
|
||||
return valueForDisplay;
|
||||
}, [t]);
|
||||
|
||||
const handleLogin = async () => {
|
||||
const result = await onLogin();
|
||||
if (result) {
|
||||
setExpanded(false);
|
||||
info(t("Login.UserChanged"));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={classes.loggedInContent}>
|
||||
<Card className={classes.loggedInCard}>
|
||||
<CardHeader
|
||||
avatar={
|
||||
<Avatar aria-label="user" className={classes.avatar}>
|
||||
<AccountBox />
|
||||
</Avatar>
|
||||
}
|
||||
title={<strong>{t("Login.Hello", { username: userName })}</strong>}
|
||||
subheader={
|
||||
<Tooltip title={t("Login.AuthenticationDate")}>
|
||||
<Typography variant="caption" display="block">
|
||||
{loginDate}
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
}
|
||||
/>
|
||||
|
||||
<CardActions disableSpacing>
|
||||
<Tooltip title={t("Login.ChangeUser")}>
|
||||
<IconButton
|
||||
size="small"
|
||||
className={classes.onRight}
|
||||
onClick={handleExpandLogin}
|
||||
aria-expanded={expanded}
|
||||
aria-label="show login component"
|
||||
>
|
||||
<RotateLeft />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip title={t("Login.Logout")}>
|
||||
<IconButton size="small" onClick={onLogout}>
|
||||
<ExitToApp />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</CardActions>
|
||||
<Divider />
|
||||
<Collapse in={expanded} timeout="auto" unmountOnExit>
|
||||
<CardContent
|
||||
className={classes.collapseContent}
|
||||
style={{ paddingBottom: "5px" }}
|
||||
>
|
||||
<LoginComponent
|
||||
credentials={credentials}
|
||||
onChange={onChange}
|
||||
onLogin={handleLogin}
|
||||
/>
|
||||
</CardContent>
|
||||
</Collapse>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
LoggedInComponent.propTypes = {
|
||||
credentials: PropTypes.object.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
onLogin: PropTypes.func.isRequired,
|
||||
onLogout: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default LoggedInComponent;
|
@ -2,7 +2,8 @@ import React, { useState } from "react";
|
||||
import LoginCard from "./LoginCard";
|
||||
import { useToast } from "../../../hooks";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useTuitioClient } from "@flare/tuitio-client-react";
|
||||
import LoggedInComponent from "./LoggedInComponent";
|
||||
import { useTuitioClient, useTuitioToken } from "@flare/tuitio-client-react";
|
||||
|
||||
const LoginContainer = () => {
|
||||
const [credentials, setCredentials] = useState({
|
||||
@ -12,10 +13,11 @@ const LoginContainer = () => {
|
||||
|
||||
const { error } = useToast();
|
||||
const { t } = useTranslation();
|
||||
const { login } = useTuitioClient({
|
||||
const { login, logout } = useTuitioClient({
|
||||
onLoginFailed: () => error(t("Login.IncorrectCredentials")),
|
||||
onLoginError: err => error(err.message)
|
||||
});
|
||||
const { valid: tokenIsValid } = useTuitioToken();
|
||||
|
||||
const handleChange = prop => event => {
|
||||
setCredentials(prev => ({ ...prev, [prop]: event.target.value }));
|
||||
@ -26,12 +28,27 @@ const LoginContainer = () => {
|
||||
return login(userName, password);
|
||||
};
|
||||
|
||||
const handleLogout = () => {
|
||||
logout();
|
||||
};
|
||||
|
||||
return (
|
||||
<LoginCard
|
||||
credentials={credentials}
|
||||
onChange={handleChange}
|
||||
onLogin={handleLogin}
|
||||
/>
|
||||
<>
|
||||
{tokenIsValid ? (
|
||||
<LoggedInComponent
|
||||
credentials={credentials}
|
||||
onChange={handleChange}
|
||||
onLogin={handleLogin}
|
||||
onLogout={handleLogout}
|
||||
/>
|
||||
) : (
|
||||
<LoginCard
|
||||
credentials={credentials}
|
||||
onChange={handleChange}
|
||||
onLogin={handleLogin}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,7 +1,23 @@
|
||||
const styles = theme => ({
|
||||
loggedInCard: {
|
||||
minWidth: 350
|
||||
},
|
||||
onRight: {
|
||||
marginLeft: "auto"
|
||||
},
|
||||
avatar: {
|
||||
backgroundColor: theme.palette.primary.main
|
||||
},
|
||||
collapseContent: {
|
||||
padding: 0
|
||||
},
|
||||
loggedInContent: {
|
||||
minHeight: "80vh",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
fontSize: "calc(10px + 2vmin)"
|
||||
},
|
||||
appLogin: {
|
||||
minHeight: "100vh",
|
||||
display: "flex",
|
||||
|
148
src/features/machines/components/Machine.js
Normal file
148
src/features/machines/components/Machine.js
Normal file
@ -0,0 +1,148 @@
|
||||
import React, { useMemo } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import {
|
||||
TableCell,
|
||||
TableRow,
|
||||
IconButton,
|
||||
Collapse,
|
||||
Tooltip,
|
||||
Menu
|
||||
} from "@material-ui/core";
|
||||
import { KeyboardArrowDown, KeyboardArrowUp } from "@material-ui/icons";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import MachineLog from "./MachineLog";
|
||||
import WakeComponent from "./WakeComponent";
|
||||
import { useSensitiveInfo } from "../../../hooks";
|
||||
|
||||
const useRowStyles = makeStyles({
|
||||
root: {
|
||||
"& > *": {
|
||||
borderBottom: "unset"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
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>
|
||||
);
|
||||
});
|
||||
|
||||
ActionButton.propTypes = {
|
||||
machine: PropTypes.shape({
|
||||
machineId: PropTypes.number.isRequired
|
||||
}).isRequired,
|
||||
action: PropTypes.shape({
|
||||
code: PropTypes.string.isRequired,
|
||||
tooltip: PropTypes.string.isRequired,
|
||||
system: PropTypes.bool,
|
||||
effect: PropTypes.func.isRequired
|
||||
}).isRequired
|
||||
};
|
||||
|
||||
const Machine = ({
|
||||
machine,
|
||||
actions,
|
||||
logs,
|
||||
addLog,
|
||||
secondaryActionsMenuProps
|
||||
}) => {
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const classes = useRowStyles();
|
||||
const { mask, maskElements } = useSensitiveInfo();
|
||||
|
||||
const topActions = useMemo(
|
||||
() => actions.filter(a => a.top === true),
|
||||
[actions]
|
||||
);
|
||||
|
||||
const secondaryActions = useMemo(
|
||||
() => actions.filter(a => a.top === false),
|
||||
[actions]
|
||||
);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<TableRow className={classes.root}>
|
||||
<TableCell>
|
||||
<IconButton
|
||||
aria-label="expand row"
|
||||
size="small"
|
||||
onClick={() => setOpen(!open)}
|
||||
>
|
||||
{open ? <KeyboardArrowUp /> : <KeyboardArrowDown />}
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
<TableCell component="th" scope="row">
|
||||
{mask(machine.fullMachineName)}
|
||||
</TableCell>
|
||||
<TableCell>{mask(machine.machineName)}</TableCell>
|
||||
<TableCell>{mask(machine.iPv4Address)}</TableCell>
|
||||
<TableCell>{mask(machine.macAddress)}</TableCell>
|
||||
<TableCell align="right">
|
||||
<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>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell style={{ paddingBottom: 0, paddingTop: 0 }} colSpan={6}>
|
||||
<Collapse in={open} timeout="auto" unmountOnExit>
|
||||
<MachineLog logs={maskElements(logs)} />
|
||||
</Collapse>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
Machine.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,
|
||||
logs: PropTypes.array.isRequired,
|
||||
addLog: PropTypes.func.isRequired,
|
||||
secondaryActionsMenuProps: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
export default Machine;
|
@ -1,110 +0,0 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import {
|
||||
Accordion,
|
||||
AccordionSummary,
|
||||
AccordionDetails,
|
||||
Grid
|
||||
} from "@material-ui/core";
|
||||
import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
|
||||
import withStyles from "@material-ui/core/styles/withStyles";
|
||||
import MachineLog from "./common/MachineLog";
|
||||
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(theme => ({
|
||||
root: {
|
||||
minHeight: "20px",
|
||||
height: "42px",
|
||||
[theme.breakpoints.down("md")]: {
|
||||
height: "62px"
|
||||
},
|
||||
[theme.breakpoints.down("sm")]: {
|
||||
height: "102px"
|
||||
}
|
||||
},
|
||||
expandIcon: {
|
||||
order: -1
|
||||
}
|
||||
}))(AccordionSummary);
|
||||
|
||||
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 }) => {
|
||||
const { t } = useTranslation();
|
||||
const classes = useStyles();
|
||||
return (
|
||||
<Accordion>
|
||||
<IconLeftAccordionSummary
|
||||
expandIcon={<ExpandMoreIcon />}
|
||||
aria-label="Expand"
|
||||
aria-controls="additional-actions1-content"
|
||||
id="additional-actions1-header"
|
||||
IconButtonProps={{ edge: "start" }}
|
||||
>
|
||||
<Grid container className={classes.panel}>
|
||||
<Grid item xs={11}>
|
||||
<Grid container>
|
||||
<GridCell
|
||||
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}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</IconLeftAccordionSummary>
|
||||
<AccordionDetails>
|
||||
<MachineLog logs={logs} />
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
);
|
||||
};
|
||||
|
||||
MachineAccordion.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,
|
||||
logs: PropTypes.array.isRequired,
|
||||
addLog: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default MachineAccordion;
|
@ -1,15 +1,21 @@
|
||||
import React, { useState, useCallback } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import MachineTableRow from "./MachineTableRow";
|
||||
import MachineAccordion from "./MachineAccordion";
|
||||
import { ViewModes } from "./ViewModeSelection";
|
||||
import Machine from "./Machine";
|
||||
import { useToast } from "../../../hooks";
|
||||
import { LastPage, RotateLeft, Launch, Stop } from "@material-ui/icons";
|
||||
import {
|
||||
LastPage,
|
||||
MoreHoriz,
|
||||
RotateLeft,
|
||||
Launch,
|
||||
Stop
|
||||
} from "@material-ui/icons";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import useApi from "../../../api";
|
||||
|
||||
const MachineContainer = ({ machine, viewMode }) => {
|
||||
const MachineContainer = ({ machine }) => {
|
||||
const [logs, setLogs] = useState([]);
|
||||
const [secondaryActionsAnchor, setSecondaryActionsAnchor] =
|
||||
React.useState(null);
|
||||
|
||||
const { success, error } = useToast();
|
||||
const { t } = useTranslation();
|
||||
@ -36,7 +42,7 @@ const MachineContainer = ({ machine, viewMode }) => {
|
||||
);
|
||||
|
||||
const pingMachine = useCallback(
|
||||
async machine => {
|
||||
machine => async () => {
|
||||
await api.pingMachine(machine.machineId, {
|
||||
onCompleted: manageActionResponse
|
||||
});
|
||||
@ -44,20 +50,35 @@ const MachineContainer = ({ machine, viewMode }) => {
|
||||
[manageActionResponse, api]
|
||||
);
|
||||
|
||||
const handleOpenSecondaryActions = event => {
|
||||
setSecondaryActionsAnchor(event.currentTarget);
|
||||
};
|
||||
|
||||
const handleCloseSecondaryActions = () => {
|
||||
setSecondaryActionsAnchor(null);
|
||||
};
|
||||
|
||||
const secondaryActionsMenuProps = {
|
||||
anchor: secondaryActionsAnchor,
|
||||
onCloseSecondaryActions: handleCloseSecondaryActions
|
||||
};
|
||||
|
||||
const shutdownMachine = useCallback(
|
||||
async machine => {
|
||||
machine => async () => {
|
||||
await api.shutdownMachine(machine.machineId, 0, false, {
|
||||
onCompleted: manageActionResponse
|
||||
});
|
||||
handleCloseSecondaryActions();
|
||||
},
|
||||
[manageActionResponse, api]
|
||||
);
|
||||
|
||||
const restartMachine = useCallback(
|
||||
async machine => {
|
||||
machine => async () => {
|
||||
await api.restartMachine(machine.machineId, 0, false, {
|
||||
onCompleted: manageActionResponse
|
||||
});
|
||||
handleCloseSecondaryActions();
|
||||
},
|
||||
[manageActionResponse, api]
|
||||
);
|
||||
@ -68,56 +89,52 @@ const MachineContainer = ({ machine, viewMode }) => {
|
||||
effect: pingMachine,
|
||||
icon: LastPage,
|
||||
tooltip: t("Machine.Actions.Ping"),
|
||||
main: true
|
||||
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"),
|
||||
main: false
|
||||
top: false
|
||||
},
|
||||
{
|
||||
code: "restart",
|
||||
effect: restartMachine,
|
||||
icon: RotateLeft,
|
||||
tooltip: t("Machine.Actions.Restart"),
|
||||
main: false
|
||||
top: false
|
||||
},
|
||||
{
|
||||
code: "advanced",
|
||||
effect: () => {},
|
||||
icon: Launch,
|
||||
tooltip: t("Machine.Actions.Advanced"),
|
||||
main: false
|
||||
top: false
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
{viewMode === ViewModes.TABLE && (
|
||||
<MachineTableRow
|
||||
machine={machine}
|
||||
actions={actions}
|
||||
logs={logs}
|
||||
addLog={addLog}
|
||||
/>
|
||||
)}
|
||||
{viewMode === ViewModes.ACCORDION && (
|
||||
<MachineAccordion
|
||||
machine={machine}
|
||||
actions={actions}
|
||||
logs={logs}
|
||||
addLog={addLog}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
<Machine
|
||||
machine={machine}
|
||||
actions={actions}
|
||||
logs={logs}
|
||||
addLog={addLog}
|
||||
secondaryActionsMenuProps={secondaryActionsMenuProps}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
MachineContainer.propTypes = {
|
||||
machine: PropTypes.object.isRequired,
|
||||
viewMode: PropTypes.string.isRequired
|
||||
machine: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
export default MachineContainer;
|
||||
|
@ -1,19 +1,15 @@
|
||||
import React, { useMemo } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { Box } from "@material-ui/core";
|
||||
import { useSensitiveInfo } from "../../../../hooks";
|
||||
import { LazyLog, ScrollFollow } from "react-lazylog";
|
||||
|
||||
const MachineLog = ({ logs }) => {
|
||||
const { maskElements } = useSensitiveInfo();
|
||||
|
||||
const displayLogs = useMemo(
|
||||
() => (logs.length > 0 ? maskElements(logs).join("\n") : "..."),
|
||||
[logs, maskElements]
|
||||
() => (logs.length > 0 ? logs.join("\n") : "..."),
|
||||
[logs]
|
||||
);
|
||||
|
||||
return (
|
||||
<Box width="100%">
|
||||
<Box margin={1}>
|
||||
<div style={{ height: 200 }}>
|
||||
<ScrollFollow
|
||||
startFollowing={true}
|
@ -1,70 +0,0 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { TableCell, TableRow, IconButton, Collapse } from "@material-ui/core";
|
||||
import { KeyboardArrowDown, KeyboardArrowUp } from "@material-ui/icons";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import MachineLog from "./common/MachineLog";
|
||||
import { useSensitiveInfo } from "../../../hooks";
|
||||
import ActionsGroup from "./common/ActionsGroup";
|
||||
|
||||
const useRowStyles = makeStyles({
|
||||
root: {
|
||||
"& > *": {
|
||||
borderBottom: "unset"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const MachineTableRow = ({ machine, actions, logs, addLog }) => {
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const classes = useRowStyles();
|
||||
const { mask } = useSensitiveInfo();
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<TableRow className={classes.root}>
|
||||
<TableCell>
|
||||
<IconButton
|
||||
aria-label="expand row"
|
||||
size="small"
|
||||
onClick={() => setOpen(!open)}
|
||||
>
|
||||
{open ? <KeyboardArrowUp /> : <KeyboardArrowDown />}
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
<TableCell component="th" scope="row">
|
||||
{mask(machine.fullMachineName)}
|
||||
</TableCell>
|
||||
<TableCell>{mask(machine.machineName)}</TableCell>
|
||||
<TableCell>{mask(machine.iPv4Address)}</TableCell>
|
||||
<TableCell>{mask(machine.macAddress)}</TableCell>
|
||||
<TableCell align="right">
|
||||
<ActionsGroup machine={machine} actions={actions} addLog={addLog} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell style={{ paddingBottom: 0, paddingTop: 0 }} colSpan={6}>
|
||||
<Collapse in={open} timeout="auto" unmountOnExit>
|
||||
<MachineLog logs={logs} />
|
||||
</Collapse>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
MachineTableRow.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,
|
||||
logs: PropTypes.array.isRequired,
|
||||
addLog: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default MachineTableRow;
|
@ -1,19 +1,16 @@
|
||||
import React, { useContext, useEffect, useCallback, useState } from "react";
|
||||
import React, { useContext, useEffect, useCallback } from "react";
|
||||
import {
|
||||
NetworkStateContext,
|
||||
NetworkDispatchContext
|
||||
} from "../../network/state/contexts";
|
||||
ApplicationStateContext,
|
||||
ApplicationDispatchContext
|
||||
} from "../../../state/contexts";
|
||||
import useApi from "../../../api";
|
||||
import MachinesListComponent from "./MachinesListComponent";
|
||||
import MachinesList from "./MachinesList";
|
||||
import PageTitle from "../../../components/common/PageTitle";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import ViewModeSelection, { ViewModes } from "./ViewModeSelection";
|
||||
|
||||
const MachinesContainer = () => {
|
||||
const [viewMode, setViewMode] = useState(null);
|
||||
|
||||
const state = useContext(NetworkStateContext);
|
||||
const dispatchActions = useContext(NetworkDispatchContext);
|
||||
const state = useContext(ApplicationStateContext);
|
||||
const dispatchActions = useContext(ApplicationDispatchContext);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const api = useApi();
|
||||
@ -35,21 +32,8 @@ const MachinesContainer = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageTitle
|
||||
text={t("Menu.Machines")}
|
||||
toolBar={
|
||||
<ViewModeSelection
|
||||
callback={setViewMode}
|
||||
initialMode={ViewModes.TABLE}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
{viewMode && (
|
||||
<MachinesListComponent
|
||||
machines={state.network.machines}
|
||||
viewMode={viewMode}
|
||||
/>
|
||||
)}
|
||||
<PageTitle text={t("Menu.Machines")} />
|
||||
<MachinesList dense={true} machines={state.network.machines} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
48
src/features/machines/components/MachinesList.js
Normal file
48
src/features/machines/components/MachinesList.js
Normal file
@ -0,0 +1,48 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TableRow
|
||||
} from "@material-ui/core";
|
||||
import Paper from "@material-ui/core/Paper";
|
||||
import MachineContainer from "./MachineContainer";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const MachinesList = ({ dense, machines }) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<TableContainer component={Paper}>
|
||||
<Table aria-label="collapsible table" size={dense ? "small" : "medium"}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell />
|
||||
<TableCell>{t("Machine.FullName")}</TableCell>
|
||||
<TableCell>{t("Machine.Name")}</TableCell>
|
||||
<TableCell>{t("Machine.IP")}</TableCell>
|
||||
<TableCell>{t("Machine.MAC")}</TableCell>
|
||||
<TableCell align="right" />
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{machines.map(machine => (
|
||||
<MachineContainer
|
||||
key={`machine-${machine.machineId}`}
|
||||
machine={machine}
|
||||
/>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
);
|
||||
};
|
||||
|
||||
MachinesList.propTypes = {
|
||||
dense: PropTypes.bool.isRequired,
|
||||
machines: PropTypes.array.isRequired
|
||||
};
|
||||
|
||||
export default MachinesList;
|
@ -1,80 +0,0 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TableRow
|
||||
} from "@material-ui/core";
|
||||
import Paper from "@material-ui/core/Paper";
|
||||
import MachineContainer from "./MachineContainer";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ViewModes } from "./ViewModeSelection";
|
||||
|
||||
const MachinesList = ({ machines, viewMode }) => {
|
||||
return (
|
||||
<>
|
||||
{machines.map(machine => (
|
||||
<MachineContainer
|
||||
key={`machine-${machine.machineId}`}
|
||||
machine={machine}
|
||||
viewMode={viewMode}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
MachinesList.propTypes = {
|
||||
machines: PropTypes.array.isRequired,
|
||||
viewMode: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
const MachinesTableList = ({ machines, viewMode }) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<TableContainer component={Paper}>
|
||||
<Table aria-label="collapsible table" size="small">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell />
|
||||
<TableCell>{t("Machine.FullName")}</TableCell>
|
||||
<TableCell>{t("Machine.Name")}</TableCell>
|
||||
<TableCell>{t("Machine.IP")}</TableCell>
|
||||
<TableCell>{t("Machine.MAC")}</TableCell>
|
||||
<TableCell align="right" />
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
<MachinesList machines={machines} viewMode={viewMode} />
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
);
|
||||
};
|
||||
|
||||
MachinesTableList.propTypes = {
|
||||
machines: PropTypes.array.isRequired,
|
||||
viewMode: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
const MachinesListComponent = ({ machines, viewMode }) => {
|
||||
return (
|
||||
<>
|
||||
{viewMode === ViewModes.TABLE ? (
|
||||
<MachinesTableList machines={machines} viewMode={viewMode} />
|
||||
) : (
|
||||
<MachinesList machines={machines} viewMode={viewMode} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
MachinesListComponent.propTypes = {
|
||||
machines: PropTypes.array.isRequired,
|
||||
viewMode: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
export default MachinesListComponent;
|
@ -1,80 +0,0 @@
|
||||
import React, { useState, useEffect, useCallback } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import TableChartIcon from "@material-ui/icons/TableChart";
|
||||
import ViewListIcon from "@material-ui/icons/ViewList";
|
||||
import ToggleButton from "@material-ui/lab/ToggleButton";
|
||||
import ToggleButtonGroup from "@material-ui/lab/ToggleButtonGroup";
|
||||
import { Tooltip } from "@material-ui/core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export const ViewModes = {
|
||||
TABLE: "table",
|
||||
ACCORDION: "accordion"
|
||||
};
|
||||
|
||||
const ViewModeSelection = ({ initialMode, callback }) => {
|
||||
const [state, setState] = useState({
|
||||
mode: initialMode,
|
||||
manual: false
|
||||
});
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleViewModeSelection = useCallback((event, mode) => {
|
||||
setState({ mode, manual: true });
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const mediaQuery = window.matchMedia("(max-width: 1100px)");
|
||||
|
||||
function handleMatches(event) {
|
||||
if (state.manual === true) return;
|
||||
const mode = event.matches ? ViewModes.ACCORDION : ViewModes.TABLE;
|
||||
setState({ mode, manual: false });
|
||||
}
|
||||
|
||||
handleMatches(mediaQuery);
|
||||
mediaQuery.addListener(handleMatches);
|
||||
|
||||
return () => {
|
||||
mediaQuery.removeListener(handleMatches);
|
||||
};
|
||||
}, [state.manual]);
|
||||
|
||||
useEffect(() => callback && callback(state.mode), [callback, state.mode]);
|
||||
|
||||
return (
|
||||
<ToggleButtonGroup
|
||||
size="small"
|
||||
value={state.mode}
|
||||
exclusive
|
||||
onChange={handleViewModeSelection}
|
||||
>
|
||||
<ToggleButton
|
||||
value={ViewModes.TABLE}
|
||||
aria-label="table view mode"
|
||||
disabled={state.mode === ViewModes.TABLE}
|
||||
>
|
||||
<Tooltip title={t("ViewModes.Table")}>
|
||||
<TableChartIcon />
|
||||
</Tooltip>
|
||||
</ToggleButton>
|
||||
<ToggleButton
|
||||
value={ViewModes.ACCORDION}
|
||||
aria-label="accordion view mode"
|
||||
disabled={state.mode === ViewModes.ACCORDION}
|
||||
>
|
||||
<Tooltip title={t("ViewModes.List")}>
|
||||
<ViewListIcon />
|
||||
</Tooltip>
|
||||
</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
);
|
||||
};
|
||||
|
||||
ViewModeSelection.propTypes = {
|
||||
initialMode: PropTypes.oneOf([ViewModes.TABLE, ViewModes.ACCORDION]),
|
||||
callback: PropTypes.func
|
||||
};
|
||||
|
||||
export default ViewModeSelection;
|
@ -3,9 +3,9 @@ import PropTypes from "prop-types";
|
||||
import { IconButton, Tooltip } from "@material-ui/core";
|
||||
import { PowerSettingsNew } from "@material-ui/icons";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useToast } from "../../../../hooks";
|
||||
import { msToMinAndSec } from "../../../../utils/time";
|
||||
import useApi from "../../../../api";
|
||||
import { useToast } from "../../../hooks";
|
||||
import { msToMinAndSec } from "../../../utils/time";
|
||||
import useApi from "../../../api";
|
||||
|
||||
const initialState = { on: false };
|
||||
const defaultPingInterval = 1200000; //20 minutes
|
||||
@ -80,11 +80,6 @@ const WakeComponent = ({ machine, addLog }) => {
|
||||
|
||||
useEffect(pingInLoop, [trigger, pingInLoop]);
|
||||
|
||||
const handleWakeClick = event => {
|
||||
wakeMachine();
|
||||
event.stopPropagation();
|
||||
};
|
||||
|
||||
return (
|
||||
<Tooltip title={t(state.on ? "Machine.PoweredOn" : "Machine.Actions.Wake")}>
|
||||
<span>
|
||||
@ -92,9 +87,8 @@ const WakeComponent = ({ machine, addLog }) => {
|
||||
id={`machine-${machine.machineId}-wake`}
|
||||
size={"small"}
|
||||
disabled={state.on}
|
||||
onClick={handleWakeClick}
|
||||
onClick={wakeMachine}
|
||||
style={state.on ? { color: "#33cc33" } : {}}
|
||||
onFocus={event => event.stopPropagation()}
|
||||
>
|
||||
<PowerSettingsNew />
|
||||
</IconButton>
|
@ -1,45 +0,0 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { IconButton, Tooltip } from "@material-ui/core";
|
||||
|
||||
const ActionButton = React.forwardRef((props, _ref) => {
|
||||
const { action, machine, callback } = props;
|
||||
const id = `machine-item-${machine.machineId}-${action.code}`;
|
||||
const handleActionClick = event => {
|
||||
action.effect(machine, event);
|
||||
callback && callback(machine);
|
||||
event.stopPropagation();
|
||||
};
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
id={`machine-item-${machine.machineId}-${action.code}-tooltip`}
|
||||
title={action.tooltip}
|
||||
>
|
||||
<span>
|
||||
<IconButton
|
||||
id={id}
|
||||
size={"small"}
|
||||
onFocus={event => event.stopPropagation()}
|
||||
onClick={handleActionClick}
|
||||
>
|
||||
<action.icon />
|
||||
</IconButton>
|
||||
</span>
|
||||
</Tooltip>
|
||||
);
|
||||
});
|
||||
|
||||
ActionButton.propTypes = {
|
||||
machine: PropTypes.shape({
|
||||
machineId: PropTypes.number.isRequired
|
||||
}).isRequired,
|
||||
action: PropTypes.shape({
|
||||
code: PropTypes.string.isRequired,
|
||||
tooltip: PropTypes.string.isRequired,
|
||||
effect: PropTypes.func.isRequired
|
||||
}).isRequired,
|
||||
callback: PropTypes.func
|
||||
};
|
||||
|
||||
export default ActionButton;
|
@ -1,85 +0,0 @@
|
||||
import React, { useMemo, useState } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import WakeComponent from "./WakeComponent";
|
||||
import ActionButton from "./ActionButton";
|
||||
import { Menu } from "@material-ui/core";
|
||||
import { MoreHoriz } from "@material-ui/icons";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const ActionsGroup = ({ machine, actions, addLog }) => {
|
||||
const [menuAnchor, setMenuAnchor] = useState(null);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const mainActions = useMemo(
|
||||
() => actions.filter(a => a.main === true),
|
||||
[actions]
|
||||
);
|
||||
|
||||
const secondaryActions = useMemo(
|
||||
() => actions.filter(a => a.main === false),
|
||||
[actions]
|
||||
);
|
||||
|
||||
const handleMenuOpen = (_, event) => {
|
||||
setMenuAnchor(event.currentTarget);
|
||||
};
|
||||
|
||||
const handleMenuClose = () => {
|
||||
setMenuAnchor(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<WakeComponent machine={machine} addLog={addLog} />
|
||||
{mainActions.map(action => (
|
||||
<ActionButton
|
||||
key={`machine-item-${machine.machineId}-${action.code}`}
|
||||
action={action}
|
||||
machine={machine}
|
||||
/>
|
||||
))}
|
||||
<ActionButton
|
||||
action={{
|
||||
code: "more",
|
||||
effect: handleMenuOpen,
|
||||
icon: MoreHoriz,
|
||||
tooltip: t("Machine.Actions.More")
|
||||
}}
|
||||
machine={machine}
|
||||
/>
|
||||
|
||||
<Menu
|
||||
id="secondary-actions-menu"
|
||||
anchorEl={menuAnchor}
|
||||
keepMounted
|
||||
open={Boolean(menuAnchor)}
|
||||
onClose={handleMenuClose}
|
||||
>
|
||||
{secondaryActions.map(action => (
|
||||
<ActionButton
|
||||
key={`machine-item-${machine.machineId}-${action.code}`}
|
||||
action={action}
|
||||
machine={machine}
|
||||
callback={handleMenuClose}
|
||||
/>
|
||||
))}
|
||||
</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
|
||||
};
|
||||
|
||||
export default ActionsGroup;
|
@ -1,12 +1,19 @@
|
||||
import React from "react";
|
||||
import MachinesContainer from "../../machines/components/MachinesContainer";
|
||||
import NetworkStateProvider from "../state/NetworkStateProvider";
|
||||
//import NotesContainer from "../../notes/components/NotesContainer";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import styles from "../styles";
|
||||
|
||||
const useStyles = makeStyles(styles);
|
||||
|
||||
const NetworkContainer = () => {
|
||||
const classes = useStyles();
|
||||
|
||||
return (
|
||||
<NetworkStateProvider>
|
||||
<div className={classes.root}>
|
||||
<MachinesContainer />
|
||||
</NetworkStateProvider>
|
||||
{/* <NotesContainer /> */}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,27 +0,0 @@
|
||||
import React, { useReducer, useMemo } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { NetworkStateContext, NetworkDispatchContext } from "./contexts";
|
||||
import { reducer, dispatchActions as reducerDispatchActions } from "./reducer";
|
||||
import { initialState } from "./initialState";
|
||||
|
||||
const NetworkStateProvider = ({ children }) => {
|
||||
const [state, dispatch] = useReducer(reducer, initialState);
|
||||
const dispatchActions = useMemo(
|
||||
() => reducerDispatchActions(dispatch),
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
return (
|
||||
<NetworkStateContext.Provider value={state}>
|
||||
<NetworkDispatchContext.Provider value={dispatchActions}>
|
||||
{children}
|
||||
</NetworkDispatchContext.Provider>
|
||||
</NetworkStateContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
NetworkStateProvider.propTypes = {
|
||||
children: PropTypes.node.isRequired
|
||||
};
|
||||
|
||||
export default NetworkStateProvider;
|
@ -1,4 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
export const NetworkStateContext = React.createContext();
|
||||
export const NetworkDispatchContext = React.createContext();
|
7
src/features/network/styles.js
Normal file
7
src/features/network/styles.js
Normal file
@ -0,0 +1,7 @@
|
||||
const styles = () => ({
|
||||
root: {
|
||||
margin: "15px"
|
||||
}
|
||||
});
|
||||
|
||||
export default styles;
|
33
src/features/notes/components/NotesContainer.js
Normal file
33
src/features/notes/components/NotesContainer.js
Normal file
@ -0,0 +1,33 @@
|
||||
import React, { useContext } from "react";
|
||||
import { TextField, Button } from "@material-ui/core";
|
||||
import {
|
||||
ApplicationStateContext,
|
||||
ApplicationDispatchContext
|
||||
} from "../../../state/ApplicationContexts";
|
||||
|
||||
const NotesContainer = () => {
|
||||
const state = useContext(ApplicationStateContext);
|
||||
const dispatchActions = useContext(ApplicationDispatchContext);
|
||||
|
||||
const handleChange = prop => event => {
|
||||
dispatchActions.onNetworkChange(prop, event.target.value);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<TextField
|
||||
id="ertet"
|
||||
label="Test"
|
||||
onChange={handleChange("test")}
|
||||
value={state.network.test}
|
||||
/>
|
||||
<br />
|
||||
<br />
|
||||
<Button variant="contained" color="primary">
|
||||
Read machines
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default NotesContainer;
|
@ -1,49 +0,0 @@
|
||||
import React, { useState, useMemo } from "react";
|
||||
import BrushIcon from "@material-ui/icons/Brush";
|
||||
import NotificationsIcon from "@material-ui/icons/Notifications";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import PageTitle from "../../components/common/PageTitle";
|
||||
import NavigationButtons from "../../components/common/NavigationButtons";
|
||||
import AppearanceContainer from "./appearance/AppearanceContainer";
|
||||
import NotificationsContainer from "./notifications/NotificationsContainer";
|
||||
|
||||
const NavigationTabs = {
|
||||
APPEARANCE: "Settings.Navigation.Appearance",
|
||||
NOTIFICATIONS: "Settings.Navigation.Notifications"
|
||||
};
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
code: NavigationTabs.APPEARANCE,
|
||||
icon: BrushIcon
|
||||
},
|
||||
{
|
||||
code: NavigationTabs.NOTIFICATIONS,
|
||||
icon: NotificationsIcon
|
||||
}
|
||||
];
|
||||
|
||||
const SettingsContainer = () => {
|
||||
const [tab, setTab] = useState(NavigationTabs.APPEARANCE);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const navigationTabs = useMemo(
|
||||
() => tabs.map(z => ({ ...z, tooltip: t(z.code) })),
|
||||
[t]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageTitle
|
||||
text={t(tab)}
|
||||
navigation={
|
||||
<NavigationButtons tabs={navigationTabs} onTabChange={setTab} />
|
||||
}
|
||||
/>
|
||||
{tab === NavigationTabs.APPEARANCE && <AppearanceContainer />}
|
||||
{tab === NavigationTabs.NOTIFICATIONS && <NotificationsContainer />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default SettingsContainer;
|
@ -1,61 +0,0 @@
|
||||
import React from "react";
|
||||
import { useApplicationTheme } from "../../../providers/ThemeProvider";
|
||||
import { Grid, Paper, FormControlLabel, Switch } from "@material-ui/core";
|
||||
import LanguageContainer from "./language/LanguageContainer";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
paper: {
|
||||
paddingTop: theme.spacing(1)
|
||||
},
|
||||
language: {
|
||||
paddingLeft: theme.spacing(1)
|
||||
}
|
||||
}));
|
||||
|
||||
const AppearanceComponent = () => {
|
||||
const { isDark, onDarkModeChanged } = useApplicationTheme();
|
||||
|
||||
const classes = useStyles();
|
||||
|
||||
const handleChange = event => {
|
||||
const { checked } = event.target;
|
||||
onDarkModeChanged(checked);
|
||||
};
|
||||
|
||||
return (
|
||||
<Paper variant="outlined" className={classes.paper}>
|
||||
<Grid container spacing={0}>
|
||||
<Grid item xs={12} sm={6} md={4} lg={3}>
|
||||
<FormControlLabel
|
||||
value="start"
|
||||
control={
|
||||
<Switch
|
||||
checked={isDark}
|
||||
onChange={handleChange}
|
||||
color="secondary"
|
||||
name="dark-mode-switch"
|
||||
/>
|
||||
}
|
||||
label="Dark mode:"
|
||||
labelPlacement="start"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6} md={4} lg={3}>
|
||||
<FormControlLabel
|
||||
value="start"
|
||||
control={
|
||||
<div className={classes.language}>
|
||||
<LanguageContainer />
|
||||
</div>
|
||||
}
|
||||
label="Language:"
|
||||
labelPlacement="start"
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
|
||||
export default AppearanceComponent;
|
@ -1,8 +0,0 @@
|
||||
import React from "react";
|
||||
import AppearanceComponent from "./AppearanceComponent";
|
||||
|
||||
const AppearanceContainer = () => {
|
||||
return <AppearanceComponent />;
|
||||
};
|
||||
|
||||
export default AppearanceContainer;
|
14
src/features/settings/components/SettingsContainer.js
Normal file
14
src/features/settings/components/SettingsContainer.js
Normal file
@ -0,0 +1,14 @@
|
||||
import React from "react";
|
||||
import LanguageContainer from "./language/LanguageContainer";
|
||||
import ThemeSettings from "./ThemeSettings";
|
||||
|
||||
const SettingsContainer = () => {
|
||||
return (
|
||||
<>
|
||||
<LanguageContainer />
|
||||
<ThemeSettings />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default SettingsContainer;
|
23
src/features/settings/components/ThemeSettings.js
Normal file
23
src/features/settings/components/ThemeSettings.js
Normal file
@ -0,0 +1,23 @@
|
||||
import React from "react";
|
||||
import { useApplicationTheme } from "../../../providers/ThemeProvider";
|
||||
import { Switch } from "@material-ui/core";
|
||||
|
||||
const ThemeSettings = () => {
|
||||
const { isDark, onDarkModeChanged } = useApplicationTheme();
|
||||
|
||||
const handleChange = event => {
|
||||
const { checked } = event.target;
|
||||
onDarkModeChanged(checked);
|
||||
};
|
||||
|
||||
return (
|
||||
<Switch
|
||||
checked={isDark}
|
||||
onChange={handleChange}
|
||||
color="primary"
|
||||
name="app-theme-switch"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default ThemeSettings;
|
@ -1,8 +1,8 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import Flag from "react-flags";
|
||||
import { IconButton, Menu, MenuItem } from "@material-ui/core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { IconButton } from "@material-ui/core";
|
||||
import LanguageMenu from "./LanguageMenu";
|
||||
|
||||
const LanguageComponent = ({
|
||||
languageIsSet,
|
||||
@ -11,11 +11,8 @@ const LanguageComponent = ({
|
||||
onLanguageChange,
|
||||
onClose,
|
||||
flag,
|
||||
flagsPath
|
||||
getFlagsPath
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const open = Boolean(anchorEl);
|
||||
|
||||
return (
|
||||
<>
|
||||
<IconButton
|
||||
@ -23,7 +20,6 @@ const LanguageComponent = ({
|
||||
aria-haspopup="true"
|
||||
onClick={onMenuOpen}
|
||||
color="inherit"
|
||||
size="small"
|
||||
>
|
||||
{languageIsSet && (
|
||||
<Flag
|
||||
@ -31,33 +27,16 @@ const LanguageComponent = ({
|
||||
format="png"
|
||||
pngSize={32}
|
||||
shiny={true}
|
||||
basePath={flagsPath}
|
||||
basePath={getFlagsPath()}
|
||||
alt={flag.alt}
|
||||
/>
|
||||
)}
|
||||
</IconButton>
|
||||
<Menu
|
||||
id="language-menu"
|
||||
<LanguageMenu
|
||||
anchorEl={anchorEl}
|
||||
anchorOrigin={{
|
||||
vertical: "top",
|
||||
horizontal: "right"
|
||||
}}
|
||||
keepMounted
|
||||
transformOrigin={{
|
||||
vertical: "top",
|
||||
horizontal: "left"
|
||||
}}
|
||||
open={open}
|
||||
onLanguageChange={onLanguageChange}
|
||||
onClose={onClose}
|
||||
>
|
||||
<MenuItem onClick={onLanguageChange("ro")}>
|
||||
{t("Language.Romanian")}
|
||||
</MenuItem>
|
||||
<MenuItem onClick={onLanguageChange("en")}>
|
||||
{t("Language.English")}
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -69,7 +48,7 @@ LanguageComponent.propTypes = {
|
||||
onLanguageChange: PropTypes.func.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
flag: PropTypes.object.isRequired,
|
||||
flagsPath: PropTypes.string.isRequired
|
||||
getFlagsPath: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default LanguageComponent;
|
@ -2,10 +2,6 @@ import React, { useState, useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import LanguageComponent from "./LanguageComponent";
|
||||
|
||||
const flagsPath = process.env.PUBLIC_URL
|
||||
? `${process.env.PUBLIC_URL}/flags`
|
||||
: "flags";
|
||||
|
||||
const LanguageContainer = () => {
|
||||
const [anchorEl, setAnchorEl] = useState(null);
|
||||
|
||||
@ -38,6 +34,15 @@ const LanguageContainer = () => {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
const getFlagsPath = () => {
|
||||
const basePath = "flags";
|
||||
if (process.env.PUBLIC_URL) {
|
||||
return `${process.env.PUBLIC_URL}/${basePath}`;
|
||||
} else {
|
||||
return basePath;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<LanguageComponent
|
||||
languageIsSet={i18n.language ? true : false}
|
||||
@ -46,7 +51,7 @@ const LanguageContainer = () => {
|
||||
onLanguageChange={handleLanguageChange}
|
||||
onClose={handleClose}
|
||||
flag={flag}
|
||||
flagsPath={flagsPath}
|
||||
getFlagsPath={getFlagsPath}
|
||||
/>
|
||||
);
|
||||
};
|
42
src/features/settings/components/language/LanguageMenu.js
Normal file
42
src/features/settings/components/language/LanguageMenu.js
Normal file
@ -0,0 +1,42 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Menu, MenuItem } from "@material-ui/core";
|
||||
|
||||
const LanguageMenu = ({ anchorEl, onLanguageChange, onClose }) => {
|
||||
const { t } = useTranslation();
|
||||
const open = Boolean(anchorEl);
|
||||
|
||||
return (
|
||||
<Menu
|
||||
id="language-menu"
|
||||
anchorEl={anchorEl}
|
||||
anchorOrigin={{
|
||||
vertical: "top",
|
||||
horizontal: "right"
|
||||
}}
|
||||
keepMounted
|
||||
transformOrigin={{
|
||||
vertical: "top",
|
||||
horizontal: "right"
|
||||
}}
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
>
|
||||
<MenuItem onClick={onLanguageChange("ro")}>
|
||||
{t("Language.Romanian")}
|
||||
</MenuItem>
|
||||
<MenuItem onClick={onLanguageChange("en")}>
|
||||
{t("Language.English")}
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
);
|
||||
};
|
||||
|
||||
LanguageMenu.propTypes = {
|
||||
anchorEl: PropTypes.object,
|
||||
onLanguageChange: PropTypes.func.isRequired,
|
||||
onClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default LanguageMenu;
|
@ -1,13 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
const NotificationsContainer = () => {
|
||||
return (
|
||||
<div>
|
||||
Enable/Disable email notifications (for each one separately - when
|
||||
starting the machine, when stopping) You can go even further and have an
|
||||
advanced site where you can configure each individual machine.
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default NotificationsContainer;
|
@ -1,7 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
const MainServicesContainer = () => {
|
||||
return <div>MainServices</div>;
|
||||
};
|
||||
|
||||
export default MainServicesContainer;
|
@ -1,49 +0,0 @@
|
||||
import React, { useState, useMemo } from "react";
|
||||
import CategoryIcon from "@material-ui/icons/Category";
|
||||
import GrainIcon from "@material-ui/icons/Grain";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import PageTitle from "../../components/common/PageTitle";
|
||||
import NavigationButtons from "../../components/common/NavigationButtons";
|
||||
import MainServicesContainer from "./MainServicesContainer";
|
||||
import AgentsContainer from "./agents/AgentsContainer";
|
||||
|
||||
const NavigationTabs = {
|
||||
MAIN_SERVICES: "System.Navigation.MainServices",
|
||||
AGENTS: "System.Navigation.Agents"
|
||||
};
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
code: NavigationTabs.MAIN_SERVICES,
|
||||
icon: CategoryIcon
|
||||
},
|
||||
{
|
||||
code: NavigationTabs.AGENTS,
|
||||
icon: GrainIcon
|
||||
}
|
||||
];
|
||||
|
||||
const SystemContainer = () => {
|
||||
const [tab, setTab] = useState(NavigationTabs.MAIN_SERVICES);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const navigationTabs = useMemo(
|
||||
() => tabs.map(z => ({ ...z, tooltip: t(z.code) })),
|
||||
[t]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageTitle
|
||||
text={t(tab)}
|
||||
navigation={
|
||||
<NavigationButtons tabs={navigationTabs} onTabChange={setTab} />
|
||||
}
|
||||
/>
|
||||
{tab === NavigationTabs.MAIN_SERVICES && <MainServicesContainer />}
|
||||
{tab === NavigationTabs.AGENTS && <AgentsContainer />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default SystemContainer;
|
@ -1,7 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
const AgentsContainer = () => {
|
||||
return <div>Agents</div>;
|
||||
};
|
||||
|
||||
export default AgentsContainer;
|
@ -1,110 +0,0 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import {
|
||||
Grid,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemText,
|
||||
ListItemIcon,
|
||||
Link,
|
||||
IconButton,
|
||||
Tooltip
|
||||
} from "@material-ui/core";
|
||||
import UserProfilePicture from "./UserProfilePicture";
|
||||
import BusinessCenterIcon from "@material-ui/icons/BusinessCenter";
|
||||
import { FileCopyOutlined } from "@material-ui/icons";
|
||||
import EmailIcon from "@material-ui/icons/Email";
|
||||
import { useToast } from "../../../../hooks";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import styles from "../styles";
|
||||
|
||||
const useStyles = makeStyles(styles);
|
||||
|
||||
const UserProfileCardContent = ({ userData }) => {
|
||||
const { email, profilePictureUrl } = userData;
|
||||
const { t } = useTranslation();
|
||||
const { info } = useToast();
|
||||
const classes = useStyles();
|
||||
|
||||
const handleCopyToClipboard = url => () => {
|
||||
navigator.clipboard.writeText(url);
|
||||
info(t("Generic.CopiedToClipboard"));
|
||||
};
|
||||
|
||||
const handleEmailSending = event => {
|
||||
window.location.href = `mailto:${email}`;
|
||||
event.preventDefault();
|
||||
};
|
||||
|
||||
const userName = `${userData.firstName} ${userData.lastName}`;
|
||||
|
||||
return (
|
||||
<div className={classes.panel}>
|
||||
<UserProfilePicture userData={userData} />
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<List>
|
||||
<ListItem dense>
|
||||
<ListItemIcon>
|
||||
<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>
|
||||
<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>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
UserProfileCardContent.propTypes = {
|
||||
userData: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
export default UserProfileCardContent;
|
@ -1,47 +0,0 @@
|
||||
import React, { useMemo } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Card, CardHeader, CardContent } from "@material-ui/core";
|
||||
import { useTuitioToken } from "@flare/tuitio-client-react";
|
||||
import { camelizeKeys } from "../../../../utils/camelizeKeys";
|
||||
import PageTitle from "../../../../components/common/PageTitle";
|
||||
import UserProfileCardContent from "./UserProfileCardContent";
|
||||
|
||||
const UserProfileContainer = () => {
|
||||
const { t } = useTranslation();
|
||||
const { token } = useTuitioToken();
|
||||
|
||||
const decodedToken = useMemo(() => atob(token), [token]);
|
||||
const userData = useMemo(
|
||||
() => camelizeKeys(JSON.parse(decodedToken)),
|
||||
[decodedToken]
|
||||
);
|
||||
console.log("userData", userData);
|
||||
const userLoginDate = useMemo(
|
||||
() =>
|
||||
t("DATE_FORMAT", {
|
||||
date: { value: userData.createdAt, format: "DD-MM-YYYY HH:mm:ss" }
|
||||
}),
|
||||
[t, userData.createdAt]
|
||||
);
|
||||
|
||||
const userDescription = t("User.Profile.Description", {
|
||||
userName: `${userData.firstName} ${userData.lastName}`,
|
||||
loginDate: userLoginDate
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageTitle
|
||||
text={t("User.Profile.Hello", { userName: userData.firstName })}
|
||||
/>
|
||||
<Card>
|
||||
<CardHeader title={userData.userName} subheader={userDescription} />
|
||||
<CardContent>
|
||||
<UserProfileCardContent userData={userData} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserProfileContainer;
|
@ -1,21 +0,0 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import style from "../styles";
|
||||
import Avatar from "@material-ui/core/Avatar";
|
||||
import DefaultUserProfilePicture from "../../../../assets/images/DefaultUserProfilePicture.png";
|
||||
|
||||
const useStyles = makeStyles(style);
|
||||
|
||||
const UserProfilePicture = ({ userData }) => {
|
||||
const classes = useStyles();
|
||||
const { profilePictureUrl } = userData;
|
||||
const url = profilePictureUrl ?? DefaultUserProfilePicture;
|
||||
return <Avatar src={url} alt="..." className={classes.profilePicture} />;
|
||||
};
|
||||
|
||||
UserProfilePicture.propTypes = {
|
||||
userData: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
export default UserProfilePicture;
|
@ -1,19 +0,0 @@
|
||||
const style = theme => {
|
||||
return {
|
||||
panel: {
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
"@media (max-width: 600px)": {
|
||||
flexDirection: "column" // change direction for small screens
|
||||
}
|
||||
},
|
||||
profilePicture: {
|
||||
margin: "auto",
|
||||
display: "block",
|
||||
width: theme.spacing(25),
|
||||
height: theme.spacing(25)
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export default style;
|
13
src/index.js
13
src/index.js
@ -4,6 +4,7 @@ import ThemeProvider from "./providers/ThemeProvider";
|
||||
import CssBaseline from "@material-ui/core/CssBaseline";
|
||||
import App from "./components/App";
|
||||
import { TuitioProvider } from "@flare/tuitio-client-react";
|
||||
import ApplicationStateProvider from "./providers/ApplicationStateProvider";
|
||||
import ToastProvider from "./providers/ToastProvider";
|
||||
import SensitiveInfoProvider from "./providers/SensitiveInfoProvider";
|
||||
import "./utils/i18n";
|
||||
@ -13,11 +14,13 @@ ReactDOM.render(
|
||||
<ThemeProvider>
|
||||
<CssBaseline />
|
||||
<SensitiveInfoProvider>
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<ToastProvider>
|
||||
<App />
|
||||
</ToastProvider>
|
||||
</Suspense>
|
||||
<ApplicationStateProvider>
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<ToastProvider>
|
||||
<App />
|
||||
</ToastProvider>
|
||||
</Suspense>
|
||||
</ApplicationStateProvider>
|
||||
</SensitiveInfoProvider>
|
||||
</ThemeProvider>
|
||||
</TuitioProvider>,
|
||||
|
33
src/providers/ApplicationStateProvider.js
Normal file
33
src/providers/ApplicationStateProvider.js
Normal file
@ -0,0 +1,33 @@
|
||||
import React, { useReducer, useMemo } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import {
|
||||
ApplicationStateContext,
|
||||
ApplicationDispatchContext
|
||||
} from "../state/contexts";
|
||||
import {
|
||||
reducer,
|
||||
dispatchActions as reducerDispatchActions
|
||||
} from "../state/reducer";
|
||||
import { initialState } from "../state/initialState";
|
||||
|
||||
const ApplicationStateProvider = ({ children }) => {
|
||||
const [state, dispatch] = useReducer(reducer, initialState);
|
||||
const dispatchActions = useMemo(
|
||||
() => reducerDispatchActions(dispatch),
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
return (
|
||||
<ApplicationStateContext.Provider value={state}>
|
||||
<ApplicationDispatchContext.Provider value={dispatchActions}>
|
||||
{children}
|
||||
</ApplicationDispatchContext.Provider>
|
||||
</ApplicationStateContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
ApplicationStateProvider.propTypes = {
|
||||
children: PropTypes.node.isRequired
|
||||
};
|
||||
|
||||
export default ApplicationStateProvider;
|
@ -34,12 +34,12 @@ const useSensitiveInfo = () => {
|
||||
|
||||
const mask = text => {
|
||||
if (!enabled) return text;
|
||||
return obfuscate(text, "◾");
|
||||
return obfuscate(text, "#");
|
||||
};
|
||||
|
||||
const maskElements = list => {
|
||||
if (!enabled) return list;
|
||||
const maskedList = list.map(z => obfuscate(z, "◻️"));
|
||||
const maskedList = list.map(z => obfuscate(z, "#"));
|
||||
return maskedList;
|
||||
};
|
||||
|
||||
|
4
src/state/contexts.js
Normal file
4
src/state/contexts.js
Normal file
@ -0,0 +1,4 @@
|
||||
import React from "react";
|
||||
|
||||
export const ApplicationStateContext = React.createContext();
|
||||
export const ApplicationDispatchContext = React.createContext();
|
@ -1,5 +1,5 @@
|
||||
const primary = "#00695C";
|
||||
const secondary = "#DC7633";
|
||||
const secondary = "#FF5C93";
|
||||
const warning = "#ff9800";
|
||||
const success = "#4caf50";
|
||||
const info = "#2196f3";
|
||||
@ -10,8 +10,8 @@ const defaultTheme = {
|
||||
main: primary
|
||||
},
|
||||
secondary: {
|
||||
main: secondary
|
||||
// contrastText: "#ffcc00"
|
||||
main: secondary,
|
||||
contrastText: "#ffcc00"
|
||||
},
|
||||
warning: {
|
||||
main: warning
|
||||
|
@ -1,31 +0,0 @@
|
||||
function camelizeKeys(o) {
|
||||
var newO, origKey, newKey, value;
|
||||
if (o instanceof Array) {
|
||||
return o.map(function (value) {
|
||||
if (typeof value === "object") {
|
||||
value = camelizeKeys(value);
|
||||
}
|
||||
return value;
|
||||
});
|
||||
} else {
|
||||
newO = {};
|
||||
for (origKey in o) {
|
||||
if (Object.prototype.hasOwnProperty.call(o, origKey)) {
|
||||
newKey = (
|
||||
origKey.charAt(0).toLowerCase() + origKey.slice(1) || origKey
|
||||
).toString();
|
||||
value = o[origKey];
|
||||
if (
|
||||
value instanceof Array ||
|
||||
(value !== null && value.constructor === Object)
|
||||
) {
|
||||
value = camelizeKeys(value);
|
||||
}
|
||||
newO[newKey] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return newO;
|
||||
}
|
||||
|
||||
export { camelizeKeys };
|
@ -1,3 +0,0 @@
|
||||
import { getRandomElement } from "./random";
|
||||
|
||||
export { getRandomElement };
|
@ -1,7 +0,0 @@
|
||||
const getRandomElement = array => {
|
||||
const randomIndex = Math.floor(Math.random() * array.length);
|
||||
const element = array[randomIndex];
|
||||
return element;
|
||||
};
|
||||
|
||||
export { getRandomElement };
|
Loading…
x
Reference in New Issue
Block a user