commit a589741ec63dedeef824777c0d410775358456ee Author: rasta5man Date: Mon Apr 15 19:28:54 2024 +0200 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6521517 --- /dev/null +++ b/.gitignore @@ -0,0 +1,26 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +test.js +package-lock.json + +# testing +/coverage + +# production +/build + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/README.md b/README.md new file mode 100644 index 0000000..0a6672e --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +This is a terminal for citysys project. + +Download the project. Install it with `npm install`. + +Start the project: `npm start` +View the project in browser: http://localhost:3000 +Run tests: `npm test` +Build project: `npm run build` + diff --git a/doc/LM Terminal zadanie-1.docx b/doc/LM Terminal zadanie-1.docx new file mode 100644 index 0000000..00a340c Binary files /dev/null and b/doc/LM Terminal zadanie-1.docx differ diff --git a/jsconfig.json b/jsconfig.json new file mode 100644 index 0000000..ec9aa3f --- /dev/null +++ b/jsconfig.json @@ -0,0 +1,6 @@ +{ + "compilerOptions": { + "baseUrl": "src" + }, + "include": ["src"] +} \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..d2e0103 --- /dev/null +++ b/package.json @@ -0,0 +1,49 @@ +{ + "name": "terminal-oms.app", + "version": "0.1.0", + "private": true, + "dependencies": { + "@material-ui/core": "^4.11.3", + "@material-ui/icons": "^4.11.2", + "@testing-library/jest-dom": "^5.11.9", + "@testing-library/react": "^11.2.5", + "@testing-library/user-event": "^12.7.0", + "axios": "^0.21.1", + "bitwise": "^2.1.0", + "easy-crc": "0.0.2", + "lodash.clonedeep": "^4.5.0", + "material-ui-icons": "^1.0.0-beta.36", + "moment": "^2.29.1", + "react": "^17.0.1", + "react-animated-list": "^0.1.4", + "react-dom": "^17.0.1", + "react-draggable": "^4.4.4", + "react-redux": "^7.2.2", + "react-scripts": "4.0.2", + "redux": "^4.0.5", + "web-vitals": "^1.1.0" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test", + "eject": "react-scripts eject" + }, + "eslintConfig": { + "extends": [ + "react-app" + ] + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + } +} diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..aa069f2 --- /dev/null +++ b/public/index.html @@ -0,0 +1,43 @@ + + + + + + + + + + + + + React App + + + +
+ + + diff --git a/src/App.css b/src/App.css new file mode 100644 index 0000000..74b5e05 --- /dev/null +++ b/src/App.css @@ -0,0 +1,38 @@ +.App { + text-align: center; +} + +.App-logo { + height: 40vmin; + pointer-events: none; +} + +@media (prefers-reduced-motion: no-preference) { + .App-logo { + animation: App-logo-spin infinite 20s linear; + } +} + +.App-header { + background-color: #282c34; + min-height: 100vh; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + font-size: calc(10px + 2vmin); + color: white; +} + +.App-link { + color: #61dafb; +} + +@keyframes App-logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} diff --git a/src/App.js b/src/App.js new file mode 100644 index 0000000..bdabb19 --- /dev/null +++ b/src/App.js @@ -0,0 +1,683 @@ +import React, { useState, useEffect, useRef } from 'react'; + +import PropTypes from 'prop-types'; +import { makeStyles } from '@material-ui/core/styles'; + +import Grid from '@material-ui/core/Grid'; + +import Controls from './controls' +import Output from './output' + +import Dialog from '@material-ui/core/Dialog'; +import DialogActions from '@material-ui/core/DialogActions'; +import DialogTitle from '@material-ui/core/DialogTitle'; +import DialogContent from '@material-ui/core/DialogContent'; +import TextField from '@material-ui/core/TextField'; +import Button from '@material-ui/core/Button'; +import Box from '@material-ui/core/Box'; +import CircularProgress from '@material-ui/core/CircularProgress'; + +import Tabs from '@material-ui/core/Tabs'; +import Tab from '@material-ui/core/Tab'; + +import AppBar from '@material-ui/core/AppBar'; +import Toolbar from '@material-ui/core/Toolbar'; +import CloseIcon from '@material-ui/icons/Close'; +import Typography from '@material-ui/core/Typography'; +import IconButton from '@material-ui/core/IconButton'; + +import logo from './assets/img/business-manager.svg' +import axios from 'axios' + +import Nodes from './components/nodes/nodes'; +import Relays from './components/relays/relays'; +import DB from './util/db/db' +import findGetParameterInUrl from './util/findGetParameterInUrl'; + +import FormGroup from '@material-ui/core/FormGroup'; +import FormControlLabel from '@material-ui/core/FormControlLabel'; +import Checkbox from '@material-ui/core/Checkbox'; + +import Radio from '@material-ui/core/Radio'; +import RadioGroup from '@material-ui/core/RadioGroup'; +import FormControl from '@material-ui/core/FormControl'; +import FormLabel from '@material-ui/core/FormLabel'; + +const divOutputStyle = { + backgroundColor: 'black', + overflow: "auto", + alignItems: "left", + display: "flex", + textAlign: "left", + height: "100vh", +}; + +function TabPanel(props) { + const { children, value, index, ...other } = props; + + return ( + + ); +} + +TabPanel.propTypes = { + children: PropTypes.node, + index: PropTypes.any.isRequired, + value: PropTypes.any.isRequired, +}; + +function a11yProps(index) { + return { + id: `simple-tab-${index}`, + 'aria-controls': `simple-tabpanel-${index}`, + }; +} + +const useStyles = makeStyles((theme) => ({ + root: { + flexGrow: 1, + backgroundColor: theme.palette.background.paper, + }, +})); + +const App = (props) => { + + document.title = 'IoT Terminal 2.0.4'; + // ver 2.0.4 - pridany cct do config.js + + + const controlsRef = useRef(null); + const outputRef = useRef(null); + + const [tabValue, setTabValue] = useState(0); + const [loggedIn, setLoggedIn] = useState(false); + const [username, setUserName] = useState(""); + const [password, setPassword] = useState(""); + const [baseUrl, setBaseUrl] = useState(""); + const [error, setError] = useState(""); + const [showprogress, setShowprogress] = useState(false); + const [showDialog, setShowDialog] = useState(false); + const [autoLogin, setAutoLogin] = useState(false); + const [formType, setFormType] = useState(""); + + // if inputError[key] == null, we disable button so we can not send new settings + const [settings, setSettings] = useState({}); + const [disableButton, setDisableButton] = useState(false); + const [inputError, setInputError] = useState({}); + + const size = useWindowSize(); + + useEffect(() => { + + if(username !== "" & password !== "") + { + login(); + } + + }, [autoLogin, username, password, baseUrl]); + + const validate = (event) =>{ + + if(username === "" || password === "") return true; + return false; + }; + + const handleCloseDialog = () =>{ + setShowDialog(false); + } + + const handleCommand = (command) =>{ + setFormType(command); + setShowDialog(true); + } + + const handleSettingsChange = (event) => + { + let { name, value } = event.target; + + if(event.target.type == "number") + { + if(isNaN(parseInt(value)) || parseInt(value) < 0 ) + { + setDisableButton(true); + setInputError({ + ...inputError, + [name]: null + }); + } + else + { + setDisableButton(false); + setInputError({ + ...inputError, + [name]: value + }); + + } + } + + if(event.target.type == "checkbox") + { + value = event.target.checked; + } + + setSettings({ + ...settings, + [name]: value + }); + } + + + const makeAxiosRequest = (url, table, action, body) => + { + return new Promise((resolve, reject) => { + + let instance = axios.create(); + instance(url, + { + method: 'POST', + mode: 'no-cors', + data:{ + hostname: "localhost", + table: table, + //where: ["error_type", "MANUAL"], + action: action, + body: body + }, + headers: { + 'Access-Control-Allow-Origin': '*', + 'Content-Type': 'application/json', + //"Content-Type": "text/html;charset=UTF-8" + }, + withCredentials: false, + credentials: 'same-origin', + }) + .then(response => {resolve(response)}) + .catch(error => { + const message = error + ""; + console.error(message); + }); + + }); + } + + + const loadSettings = () => + { + let url = baseUrl + `/db`; + + makeAxiosRequest(url, "settings", "read","") + .then(response => { + + if(!response.data.success) + { + //throw response.data.message; + } + + let a = response.data; + console.log("loadSettings", a); + + setSettings(a[0]); + setInputError(a[0]); + }) + .catch(error => { + const message = error + ""; + console.error(message); + }); + } + + + const handleSetNewSettings = () => + { + let url = baseUrl + `/db`; + + makeAxiosRequest(url, "settings", "update", settings) + .then(response => { + + if(!response.data.sucess) + { + //throw response.data.message; + } + + if(response.data === 1) + // if(response.data.data === 1) + { + alert("Nove nastavenia uspesne zapisane do databazy") + } + }) + .catch(error => { + const message = error + ""; + console.error(message); + }); + } + + const login = (event) =>{ + + axios(baseUrl + "/validate", + { + method: 'POST', + mode: 'no-cors', + data:{ + username: username, + password: password, + }, + headers: { + 'Access-Control-Allow-Origin': '*', + 'Content-Type': 'application/json', + }, + withCredentials: false, + credentials: 'same-origin', + }) + .then(response => { + + console.log(response); + + if(response.data.valid) + { + setLoggedIn(true); + setShowprogress(false); + setError(""); + + loadSettings(); + } + else + { + setError("Nesprávne meno, alebo heslo"); + setShowprogress(false); + } + + }) + .catch(error => { + const message = error + ""; + + setError(message); + setShowprogress(false); + }); + + }; + + const handleLogin = (event) =>{ + + if(username === "" || password === "") + { + setError("Nie je zadané meno a heslo"); + return; + } + + setShowprogress(true); + login(); + }; + + const handleTabChange = (event, newValue) => { + setTabValue(newValue); + }; + + const handleChange = event => { + + if(event.target.name === "username") setUserName(event.target.value); + if(event.target.name === "password") setPassword(event.target.value); + }; + + const buildUi = () => + { + if(loggedIn) + { + let widgets = []; + let mainUi = ( + + + +
+ +
+
+ + +
+
+ +
+
+
+ +
+ ) + + widgets.push(mainUi); + + if(showDialog) + { + + // push all data from settings.table to grid + let allSettings = []; + + if(settings !== undefined) + { + Object.keys(settings).forEach(function(key) { + // console.log(key, settings[key]); + + if(key == "backup_on_failure" || key == "maintanace_mode") + { + allSettings.push( + ( + + } + name={key} + label={key} + /> + + ) + ) + } + else if(key == "controller_type") + { + allSettings.push( + ( + + {key} + + } label="LM" /> + } label="Unipi" /> + + + ) + ) + } + else if(['restore_from_backup', 'restore_backup_wait', 'mqtt_port', 'projects_id'].includes(key)) + { + allSettings.push( + ( + + ) + ) + } + else + { + allSettings.push( + ( + + ) + ) + } + }); + } + + let dialog = ( +
+ + + + + + + + + Nastavenia FLOW + + + + + + + + + + + + + Modal title + + + + + + {/* //! tato form robi tuto chybu v konzole: Warning: validateDOMNesting(...):
cannot appear as a descendant of

. */} + + + {allSettings} + +

+ +

+
+ +
+
+ +
+
+ +
+ + + + +
+
+ ) + + widgets.push(dialog); + } + + return widgets; + } + + if(showprogress) + { + return( +
+ + + logo + + + + +
Prihlasujem...
+

+ + + +
+
+
+ ) + } + + return loginDialog(); + } + + const loginDialog = () => + { + + return ( + +
+ +

+ logo + + + + + + +
+ +
+
+ + + + +
+ +
+ {error} +
+
+ +
+ +
+ + + + + + + + +
+
+ ); + } + + return ( +
+ {buildUi()} +
+); + +// Hook +function useWindowSize() { + // Initialize state with undefined width/height so server and client renders match + // Learn more here: https://joshwcomeau.com/react/the-perils-of-rehydration/ + + const [windowSize, setWindowSize] = useState({ + width: undefined, + height: undefined, + }); + + useEffect(() => { + + let baseUrl = ''; + if(window.location.hostname === "localhost") + { + //baseUrl = "http://localhost:5000"; + baseUrl = "http://10.0.0.5:5000"; + } + + setBaseUrl(baseUrl); + DB.backendUrl = baseUrl + `/db`; + + let username = findGetParameterInUrl("username"); + let password = findGetParameterInUrl("password"); + + if(username === undefined) username = ""; + if(username === undefined) username = ""; + + if(username !== "" && password !== "") + { + setUserName(username); + setPassword(password); + + setAutoLogin(true); + } + + + + // Handler to call on window resize + function handleResize() { + // Set window width/height to state + setWindowSize({ + width: window.innerWidth, + height: window.innerHeight, + }); + } + + // Add event listener + window.addEventListener("resize", handleResize); + + // Call handler right away so state gets updated with initial window size + handleResize(); + + // Remove event listener on cleanup + return () => window.removeEventListener("resize", handleResize); + + }, []); // Empty array ensures that effect is only run on mount + + if(controlsRef.current == null || outputRef == null); + else + { + if(windowSize.width > 600) + { + outputRef.current.style.height = "100vh"; + } + else + { + outputRef.current.style.height = (windowSize.height - controlsRef.current.clientHeight) + "px"; + } + } + + return windowSize; +} + +} + +export default App; diff --git a/src/App.test.js b/src/App.test.js new file mode 100644 index 0000000..1f03afe --- /dev/null +++ b/src/App.test.js @@ -0,0 +1,8 @@ +import { render, screen } from '@testing-library/react'; +import App from './App'; + +test('renders learn react link', () => { + render(); + const linkElement = screen.getByText(/learn react/i); + expect(linkElement).toBeInTheDocument(); +}); diff --git a/src/assets/img/business-manager.svg b/src/assets/img/business-manager.svg new file mode 100644 index 0000000..f351dc5 --- /dev/null +++ b/src/assets/img/business-manager.svg @@ -0,0 +1,17 @@ + \ No newline at end of file diff --git a/src/components/AlertDialog/AlertDialog.js b/src/components/AlertDialog/AlertDialog.js new file mode 100644 index 0000000..9013669 --- /dev/null +++ b/src/components/AlertDialog/AlertDialog.js @@ -0,0 +1,110 @@ +import React, { useEffect } from 'react'; +import Button from '@material-ui/core/Button'; +import Dialog from '@material-ui/core/Dialog'; +import DialogActions from '@material-ui/core/DialogActions'; +import DialogContent from '@material-ui/core/DialogContent'; +import DialogContentText from '@material-ui/core/DialogContentText'; +import DialogTitle from '@material-ui/core/DialogTitle'; +import Paper from '@material-ui/core/Paper'; +import Draggable from 'react-draggable'; + +import WarningIcon from '@material-ui/icons/Warning'; +import { green } from '@material-ui/core/colors'; +import { red } from '@material-ui/core/colors'; +import { blue } from '@material-ui/core/colors'; + +import ListItemIcon from '@material-ui/core/ListItemIcon'; +import Typography from '@material-ui/core/Typography'; +import MenuItem from '@material-ui/core/MenuItem'; + +import ListItemText from '@material-ui/core/ListItemText'; +import ListItem from '@material-ui/core/ListItem'; +import List from '@material-ui/core/List'; +import Divider from '@material-ui/core/Divider'; + +function PaperComponent(props) { + return ( + + + + ); +} + +export default function AlertDialog(props) { + const [open, setOpen] = React.useState(props.open); + + useEffect(() => { + setOpen(props.open); +}, [props.open]) + + const handleClickOpen = () => { + //setOpen(true); + }; + + const handleClose = () => { + //setOpen(false); + props.onConfirm(); + }; + + return ( +
+ + + + + + + + {props.title} + + + + + { + Array.isArray(props.content) + ? + ( + + { + props.content.map((message) => ( + <> + + + + + + )) + } + + ) + : + ( + + + {props.content} + + ) + } + + + + + + + + + + +
+ ); +} \ No newline at end of file diff --git a/src/components/nodes/menu.js b/src/components/nodes/menu.js new file mode 100644 index 0000000..ceee4ab --- /dev/null +++ b/src/components/nodes/menu.js @@ -0,0 +1,106 @@ +import React from 'react'; +import { withStyles } from '@material-ui/core/styles'; + +import Menu from '@material-ui/core/Menu'; +import MenuItem from '@material-ui/core/MenuItem'; +import ListItemIcon from '@material-ui/core/ListItemIcon'; + +import MenuList from '@material-ui/core/MenuList'; +import { grey } from '@material-ui/core/colors'; +import { green } from '@material-ui/core/colors'; +import { red } from '@material-ui/core/colors'; +import { blue } from '@material-ui/core/colors'; + +import AddCircleIcon from '@material-ui/icons/AddCircle'; +import DeleteForeverIcon from '@material-ui/icons/DeleteForever'; +import EditIcon from '@material-ui/icons/Edit'; +import SearchIcon from '@material-ui/icons/Search'; +import Typography from '@material-ui/core/Typography'; +import ExitToAppIcon from '@material-ui/icons/ExitToApp'; + +const StyledMenu = withStyles({ + paper: { + border: '1px solid #d3d4d5', + }, +})((props) => ( + +)); + +/* +const StyledMenuItem = withStyles((theme) => ({ + root: { + '&:focus': { + backgroundColor: theme.palette.primary.main, + '& .MuiListItemIcon-root, & .MuiListItemText-primary': { + color: theme.palette.common.white, + }, + }, + }, +}))(MenuItem); +*/ + +export default function CustomizedMenus(props) { + const [anchorEl, setAnchorEl] = React.useState(props.anchorref); + + const handleClick = (event, menu) => { + setAnchorEl(event.currentTarget); + props.menuClicked(menu); + }; + + const handleClose = () => { + setAnchorEl(null); + props.menuClosed(); + }; + + return ( +
+ + + + handleClick(event, "add")}> + + + + pridať + + handleClick(event, "edit")}> + + + + editovat + + handleClick(event, "delete")}> + + + + zmazať + + handleClick(event, "exit")}> + + + + logout + + + +
+ ); +} \ No newline at end of file diff --git a/src/components/nodes/nodes.js b/src/components/nodes/nodes.js new file mode 100644 index 0000000..8d2e602 --- /dev/null +++ b/src/components/nodes/nodes.js @@ -0,0 +1,207 @@ +import React from 'react'; + +import TableDb from 'components/table/tableDb'; +//import TableDb from '../table/tableDb'; +import DB from "util/db/db"; +//import { LogicOperator } from '../../util/db/SqlQueryBuilder'; + +//import Find from './find'; +//import Form from './form'; +import CustomizedMenus from './menu' + +import MoreVertIcon from '@material-ui/icons/MoreVert'; + +//import Card from "components/Card/Card.js"; +//import CardBody from "components/Card/CardBody.js"; +//import CardHeader from "components/Card/CardHeader.js"; + +import Card from '@material-ui/core/Card'; +import CardContent from '@material-ui/core/CardContent'; +import Paper from '@material-ui/core/Paper'; + +import SearchIcon from '@material-ui/icons/Search'; +import AddIcon from '@material-ui/icons/Add'; +import EditIcon from '@material-ui/icons/Edit'; +import Button from '@material-ui/core/Button'; +import IconButton from '@material-ui/core/IconButton'; +import Tooltip from '@material-ui/core/Tooltip'; + +import Grid from '@material-ui/core/Grid'; +import Typography from '@material-ui/core/Typography'; + +//----------------------------------------------------- + +class Nodes extends TableDb { + constructor(props) { + super(props); + + this.title = "Zoznam nodov"; + this.dbTable = "nodes"; + + this.state.columns = ["actions", "node", "tbname", "line", "status"]; + this.state.visibleColumns = ["actions", "node", "tbname", "line", "status"]; + + this.renamedColumns = { + node: "node", + note: "poznámka", + num: "počet", + + }; + } + + menuClicked(menu) + { + if(menu === "delete") + { + if(this.selectedRows.length === 0) + { + this.menuClosed(); + this.showAlertDialog("Mazanie", "Nie sú označené žiadne riadky"); + return; + } + + var r = window.confirm("Naozaj chcete zmazať?"); + if (r === false) return; + + let db = new DB(); + db.dbStartTransaction(); + + //default - odstranit z subject + let conditions = {"id": this.id}; + db.dbDelete(this.getTableName(false), conditions); + db.dbCommitTransaction(); + + let index = this.index; + + db.exec().then( + result => { + this.deleteRow(index); + }, + error => { + console.log(error); + this.showAlertDialog("Chyba", error); + } + ); + + return; + } + else if(menu === "exit") + { + this.props.logout(); + } + + let processed = super.menuClicked(menu); + if(processed) return; + } + + getTableName(withView = false) + { + //if(withView) return "view_subject"; + //return this.dbTable; + return this.dbTable; + } + + //add or edit + /* + renderForm() + { + let data = undefined; + if(this.state.data == null); + else data = this.state.data[this.index]; + + //getIndex + //console.log(data);alert(); + + let status = TableDb.TABLE_STATUS.EDIT; + if(data === undefined) + { + data = {}; + status = TableDb.TABLE_STATUS.ADD; + } + + return ( +
+ ) + + } + */ + + renderUiTable() + { + return( + + + + + + + + + + + + + + + + {this.renderCancelFilterIcon()} + + this.menuClicked("edit")} aria-label="search" color="inherit"> + + + + + + + + this.menuClicked("add")} aria-label="search" color="inherit"> + + + + + + + + + + +
+ {this.renderTable()} +
+
+
+ ) + } + + renderUiForm() + { + return this.renderForm(); + } + + renderMenu() + { + return ( + + ); + } +} + +export default Nodes; \ No newline at end of file diff --git a/src/components/relays/menu.js b/src/components/relays/menu.js new file mode 100644 index 0000000..ceee4ab --- /dev/null +++ b/src/components/relays/menu.js @@ -0,0 +1,106 @@ +import React from 'react'; +import { withStyles } from '@material-ui/core/styles'; + +import Menu from '@material-ui/core/Menu'; +import MenuItem from '@material-ui/core/MenuItem'; +import ListItemIcon from '@material-ui/core/ListItemIcon'; + +import MenuList from '@material-ui/core/MenuList'; +import { grey } from '@material-ui/core/colors'; +import { green } from '@material-ui/core/colors'; +import { red } from '@material-ui/core/colors'; +import { blue } from '@material-ui/core/colors'; + +import AddCircleIcon from '@material-ui/icons/AddCircle'; +import DeleteForeverIcon from '@material-ui/icons/DeleteForever'; +import EditIcon from '@material-ui/icons/Edit'; +import SearchIcon from '@material-ui/icons/Search'; +import Typography from '@material-ui/core/Typography'; +import ExitToAppIcon from '@material-ui/icons/ExitToApp'; + +const StyledMenu = withStyles({ + paper: { + border: '1px solid #d3d4d5', + }, +})((props) => ( + +)); + +/* +const StyledMenuItem = withStyles((theme) => ({ + root: { + '&:focus': { + backgroundColor: theme.palette.primary.main, + '& .MuiListItemIcon-root, & .MuiListItemText-primary': { + color: theme.palette.common.white, + }, + }, + }, +}))(MenuItem); +*/ + +export default function CustomizedMenus(props) { + const [anchorEl, setAnchorEl] = React.useState(props.anchorref); + + const handleClick = (event, menu) => { + setAnchorEl(event.currentTarget); + props.menuClicked(menu); + }; + + const handleClose = () => { + setAnchorEl(null); + props.menuClosed(); + }; + + return ( +
+ + + + handleClick(event, "add")}> + + + + pridať + + handleClick(event, "edit")}> + + + + editovat + + handleClick(event, "delete")}> + + + + zmazať + + handleClick(event, "exit")}> + + + + logout + + + +
+ ); +} \ No newline at end of file diff --git a/src/components/relays/relays.js b/src/components/relays/relays.js new file mode 100644 index 0000000..121d664 --- /dev/null +++ b/src/components/relays/relays.js @@ -0,0 +1,208 @@ +import React from 'react'; + +import TableDb from 'components/table/tableDb'; +//import TableDb from '../table/tableDb'; +import DB from "util/db/db"; +//import { LogicOperator } from '../../util/db/SqlQueryBuilder'; + +//import Find from './find'; +//import Form from './form'; +import CustomizedMenus from './menu' + +import MoreVertIcon from '@material-ui/icons/MoreVert'; + +//import Card from "components/Card/Card.js"; +//import CardBody from "components/Card/CardBody.js"; +//import CardHeader from "components/Card/CardHeader.js"; + +import Card from '@material-ui/core/Card'; +import CardContent from '@material-ui/core/CardContent'; +import Paper from '@material-ui/core/Paper'; + +import SearchIcon from '@material-ui/icons/Search'; +import AddIcon from '@material-ui/icons/Add'; +import EditIcon from '@material-ui/icons/Edit'; +import Button from '@material-ui/core/Button'; +import IconButton from '@material-ui/core/IconButton'; +import Tooltip from '@material-ui/core/Tooltip'; + +import Grid from '@material-ui/core/Grid'; +import Typography from '@material-ui/core/Typography'; + +//----------------------------------------------------- + +class Relays extends TableDb { + constructor(props) { + super(props); + + this.title = "Zoznam línií"; + this.dbTable = "relays"; + +//node:number|tbname:string|line:number|profile:string|processed:boolean|status:boolean + + this.state.columns = ["actions", "line", "tbname", "contactor"]; + this.state.visibleColumns = ["actions", "line", "tbname", "contactor"]; + + this.renamedColumns = { + line: "línia", + contactor: "spínač", + tbname: "tbname", + + }; + } + + menuClicked(menu) + { + if(menu === "delete") + { + if(this.selectedRows.length === 0) + { + this.menuClosed(); + this.showAlertDialog("Mazanie", "Nie sú označené žiadne riadky"); + return; + } + + var r = window.confirm("Naozaj chcete zmazať?"); + if (r === false) return; + + let db = new DB(); + db.dbStartTransaction(); + + //default - odstranit z subject + let conditions = {"id": this.id}; + db.dbDelete(this.getTableName(false), conditions); + db.dbCommitTransaction(); + + let index = this.index; + + db.exec().then( + result => { + this.deleteRow(index); + }, + error => { + console.log(error); + this.showAlertDialog("Chyba", error); + } + ); + + return; + } + else if(menu === "exit") + { + this.props.logout(); + } + + let processed = super.menuClicked(menu); + if(processed) return; + } + + getTableName(withView = false) + { + //if(withView) return "view_subject"; + //return this.dbTable; + return this.dbTable; + } + + //add or edit + /* + renderForm() + { + let data = undefined; + if(this.state.data == null); + else data = this.state.data[this.index]; + + //getIndex + //console.log(data);alert(); + + let status = TableDb.TABLE_STATUS.EDIT; + if(data === undefined) + { + data = {}; + status = TableDb.TABLE_STATUS.ADD; + } + + return ( + + ) + + } + */ + + renderUiTable() + { + return( + + + + + + + + + + + + + + + + {this.renderCancelFilterIcon()} + + this.menuClicked("edit")} aria-label="search" color="inherit"> + + + + + + + + this.menuClicked("add")} aria-label="search" color="inherit"> + + + + + + + + + +
+ {this.renderTable()} +
+
+
+ ) + } + + renderUiForm() + { + return this.renderForm(); + } + + renderMenu() + { + return ( + + ); + } +} + +export default Relays; \ No newline at end of file diff --git a/src/components/table/menu.js b/src/components/table/menu.js new file mode 100644 index 0000000..93a3d25 --- /dev/null +++ b/src/components/table/menu.js @@ -0,0 +1,106 @@ +/* default menu */ + +import React from 'react'; +import { withStyles } from '@material-ui/core/styles'; + +import Menu from '@material-ui/core/Menu'; +import MenuItem from '@material-ui/core/MenuItem'; +import ListItemIcon from '@material-ui/core/ListItemIcon'; + +import MenuList from '@material-ui/core/MenuList'; +import { green } from '@material-ui/core/colors'; +import { red } from '@material-ui/core/colors'; +import { blue } from '@material-ui/core/colors'; + +import AddCircleIcon from '@material-ui/icons/AddCircle'; +import DeleteForeverIcon from '@material-ui/icons/DeleteForever'; +import EditIcon from '@material-ui/icons/Edit'; +import SearchIcon from '@material-ui/icons/Search'; +import Typography from '@material-ui/core/Typography'; + +const StyledMenu = withStyles({ + paper: { + border: '1px solid #d3d4d5', + }, +})((props) => ( + +)); + +/* +const StyledMenuItem = withStyles((theme) => ({ + root: { + '&:focus': { + backgroundColor: theme.palette.primary.main, + '& .MuiListItemIcon-root, & .MuiListItemText-primary': { + color: theme.palette.common.white, + }, + }, + }, +}))(MenuItem); +*/ + +export default function CustomizedMenus(props) { + const [anchorEl, setAnchorEl] = React.useState(props.anchorref); + + const handleClick = (event, menu) => { + setAnchorEl(event.currentTarget); + props.menuClicked(menu); + }; + + const handleClose = () => { + setAnchorEl(null); + props.menuClosed(); + }; + + return ( +
+ + + + handleClick(event, "add")}> + + + + pridať + + handleClick(event, "edit")}> + + + + editovať + + handleClick(event, "delete")}> + + + + zmazať + + handleClick(event, "find")}> + + + + hľadať + + + +
+ ); +} \ No newline at end of file diff --git a/src/components/table/table.js b/src/components/table/table.js new file mode 100644 index 0000000..0117705 --- /dev/null +++ b/src/components/table/table.js @@ -0,0 +1,401 @@ +import React from 'react'; +import { withStyles, makeStyles } from '@material-ui/core/styles'; +import Paper from '@material-ui/core/Paper'; +import Table from '@material-ui/core/Table'; +import TableBody from '@material-ui/core/TableBody'; +import TableCell from '@material-ui/core/TableCell'; +import TableContainer from '@material-ui/core/TableContainer'; +import TableHead from '@material-ui/core/TableHead'; +import TablePagination from '@material-ui/core/TablePagination'; +import TableRow from '@material-ui/core/TableRow'; + +import Grid from '@material-ui/core/Grid'; +import CircularProgress from '@material-ui/core/CircularProgress'; +import FormControlLabel from '@material-ui/core/FormControlLabel'; +import Switch from '@material-ui/core/Switch'; + +import { blue } from '@material-ui/core/colors'; + +import TableSortLabel from '@material-ui/core/TableSortLabel'; + +import IconButton from '@material-ui/core/IconButton'; +import EditIcon from '@material-ui/icons/Edit'; +import DeleteIcon from '@material-ui/icons/Delete'; + +let sorted = []; + +function getMaxHeight(isMobile = true) +{ + let h = window.innerHeight - 250; + if(!isMobile) h = h - 40;//setDense widget + + if(h < 200) h = 200; + return h; +} + +const useStyles = makeStyles({ + root: { + width: '100%' + }, + container: { + maxHeight: getMaxHeight(), + }, +}); + +const StyledTableCell = withStyles((theme) => ({ + head: { + backgroundColor: '#6bb7fd', + color: theme.palette.common.white, + }, + body: { + fontSize: 14, + }, +}))(TableCell); + +const StyledTableRow = withStyles((theme) => ({ + root: { + '&:nth-of-type(even)': { + backgroundColor: 'gainsboro', + }, + }, +}))(TableRow); + + +function descendingComparator(a, b, orderBy) { + if (b[orderBy] < a[orderBy]) { + return -1; + } + if (b[orderBy] > a[orderBy]) { + return 1; + } + return 0; +} + +function getComparator(order, orderBy) { + return order === 'desc' + ? (a, b) => descendingComparator(a, b, orderBy) + : (a, b) => -descendingComparator(a, b, orderBy); +} + +function stableSort(array, comparator) { + const stabilizedThis = array.map((el, index) => [el, index]); + stabilizedThis.sort((a, b) => { + const order = comparator(a[0], b[0]); + if (order !== 0) return order; + return a[1] - b[1]; + }); + return stabilizedThis.map((el) => el[0]); +} + +export default function StickyHeadTable(props) { + const classes = useStyles(); + const [page, setPage] = React.useState(0); + const [rowsPerPage, setRowsPerPage] = React.useState(10); + const [order, setOrder] = React.useState('asc'); + const [orderBy, setOrderBy] = React.useState('id'); + const [dense, setDense] = React.useState(props.dense); + const [selectedRows, setSelectedRows] = React.useState([]); + const [lastIndex, setLastIndex] = React.useState(-1); + const [maxHeight, setMaxHeight] = React.useState(getMaxHeight(props.isMobile)); + const [selectableText, setSelectableText] = React.useState("none"); + + + const resizeFunction = () => { + setMaxHeight(getMaxHeight(props.isMobile)); + }; + + React.useEffect(() => { + + window.addEventListener("resize", resizeFunction); + window.addEventListener("orientationchange", resizeFunction); + // Specify how to clean up after this effect: + return function cleanup() { + + window.removeEventListener("resize", resizeFunction); + window.removeEventListener("orientationchange", resizeFunction); + }; + }, []); + + const handleChangeDense = (event) => { + setDense(event.target.checked); + + if(props.handleChangeDense !== undefined) + { + props.handleChangeDense(dense); + } + }; + + const handleChangePage = (event, newPage) => { + setPage(newPage); + }; + + const handleChangeRowsPerPage = (event) => { + setRowsPerPage(+event.target.value); + setPage(0); + }; + + const actionStyle={ + whiteSpace: "nowrap", + textOverflow: "ellipsis", + width: "70px", + display: "block", + overflow: "hidden" + } + + const showDataFromColumns = (row) => { + + let result = []; + if(props.columns.length > 0){ + for (let column of props.columns) { + if(!props.visibleColumns.includes(column)) continue; + + if(column === "actions") + { + + result.push( +
+ handleEditIconClicked(row)}> + + + handleDeleteIconClicked(row)}> + + +
+
); + // setOpen(!open)}> + //{open ? : } + // + } + else if(row.hasOwnProperty(column)){ + result.push( + {row[column]} + ); + } else { + result.push( + {null} + ); + } + } + } + return result; + } + + + const createSortHandler = (property) => (event) => { + handleRequestSort(event, property); + }; + + const handleRequestSort = (event, property) => { + const isAsc = orderBy === property && order === 'asc'; + setOrder(isAsc ? 'desc' : 'asc'); + setOrderBy(property); + }; + + const renameTableColumn = (column) =>{ + if(column in props.renamedColumns) return props.renamedColumns[column]; + return column; + } + + const isSelected = (_index) => selectedRows.indexOf(_index) !== -1; + + const handleEditIconClicked = (row) => { + + let newSelected = []; + newSelected.push(row._index); + setSelectedRows(newSelected); + + props.handleTableClick(newSelected); + props.handleTableDoubleClick(null, row.id, row._index) + }; + + const handleDeleteIconClicked = (row) => { + + let newSelected = []; + newSelected.push(row._index); + setSelectedRows(newSelected); + + props.handleTableClick(newSelected); + props.handleTableDeleteClick(null, row.id, row._index); + }; + + const handleTableClick = (event, row, index) => { + + let newSelected = []; + + //console.log("table - handleClick", row, row._index, index); + + if (event.ctrlKey) { + const indx = selectedRows.indexOf(row._index); + + newSelected = [...selectedRows]; + + if (indx === -1) { + newSelected.push(row._index); + } + else + { + newSelected.splice(indx, 1); + } + + setLastIndex(index); + + }else if (event.shiftKey) { + + if(selectedRows.length === 0) + { + newSelected.push(row._index); + } + + let fromValue = lastIndex; + let toValue = index; + + if( fromValue > toValue ) + { + fromValue = index; + toValue = lastIndex; + } + + //console.log(fromValue, toValue); + + newSelected = [...selectedRows]; + for(let indx = fromValue; indx <= toValue; indx++) + { + let row = sorted[indx]; + const t = selectedRows.indexOf(row._index); + + if (t === -1) { + newSelected.push(row._index); + } + + } + + }else { + + const indx = selectedRows.indexOf(row._index); + + if (indx === -1) { + newSelected.push(row._index); + } + else + { + selectedRows.splice(indx, 1); + } + + setLastIndex(index); + + } + + if(newSelected.length === 0) setLastIndex(index); + + setSelectedRows(newSelected); + props.handleTableClick(newSelected); + + } + + if(props.data === undefined || props.data === null) + { + return ; + } + + sorted = stableSort(props.data, getComparator(order, orderBy)).slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage); + + const handleSelectText = () => + { + selectableText == 'none' ? setSelectableText("text"): setSelectableText("none"); + } + + return ( + + + + + + + {Object.keys(props.columns).map((key, index) => { + + let column = props.columns[key]; + + //use filter? + if(!props.visibleColumns.includes(column)) return null; + + if(column === "actions") + { + return ( + + + ) + } + + return ( + + + {renameTableColumn(column)} + + + ) + }) + } + + + + {sorted.map((row, index) => { + const isItemSelected = isSelected(row._index); + + let backgroundColor; + if(isItemSelected){ + backgroundColor = blue[200]; + } + + return ( + handleTableClick(event, row, index)} + onDoubleClick={(event) => props.handleTableDoubleClick(event, row.id, row._index)} + > + {showDataFromColumns(row)} + + ) + })} + +
+
+ +
+ + { + props.isMobile ? null : } + label="Kompaktné zobrazenie" + /> + } + + } + label="Oznacovat text ?" + /> + +
+ ); +} diff --git a/src/components/table/tableDb.js b/src/components/table/tableDb.js new file mode 100644 index 0000000..bf23dff --- /dev/null +++ b/src/components/table/tableDb.js @@ -0,0 +1,582 @@ +import React, { Component } from 'react'; + +import DB from 'util/db/db'; +import cloneDeep from 'lodash.clonedeep' +import SqlQueryBuilder, { LogicOperator } from 'util/db/SqlQueryBuilder'; +import AlertDialog from 'components/AlertDialog/AlertDialog'; +import StickyHeadTable from 'components/table/table'; + +import CancelOutlinedIcon from '@material-ui/icons/CancelOutlined'; +import { red } from '@material-ui/core/colors'; +import _ from 'lodash'; + +import isMobile from 'util/isMobile'; + +import Button from '@material-ui/core/Button'; +import Tooltip from '@material-ui/core/Tooltip'; +import MoreVertIcon from '@material-ui/icons/MoreVert'; +import Typography from '@material-ui/core/Typography'; + +// card core components +//import Card from './../Card/Card'; +//import CardBody from './../Card/CardBody.js'; +//import CardHeader from './../Card/CardHeader.js'; + +import Card from '@material-ui/core/Card'; +import CardBody from '@material-ui/core/CardContent'; +import CardHeader from '@material-ui/core/CardHeader'; + +import ArrowBackIcon from '@material-ui/icons/ArrowBack'; +//----------------------------------------------------- + + +const initialState = { + data: null, + columns: [], + visibleColumns: [], + usePagination: true, + index: null, + deleteConfirmed: false, + showFindDialog: false, + showFilter: false, + showAlert: false, + alertTitle: "", + alertType: "warning", + alertContent: "", +}; + + + +class TableDb extends React.Component { + + static TABLE_STATUS = { + EDIT: 'EDIT', + ADD: 'ADD', + SHOWTABLE: 'SHOWTABLE', + } + + constructor(props) { + super(props); + + this.state = initialState; + this.state.status = TableDb.TABLE_STATUS.SHOWTABLE; + + this.db = new DB(); + this.dbTable = ""; + this.selectedRows = [];//list of indexes + + this.showFormInDialog = false; + this.fullscreen = false; + + this.id = null; + this.index = null; + + this.anchorMenuRef = React.createRef(); + + if(props.isMobile !== undefined) this.setDense(!props.isMobile); + else this.setDense(!isMobile()); + } + + display(type) + { + //to prevent rendering when add/edit is finished + if(type === "EDIT") + { + //if(this.state.rowID > 0) return null; + //if(this.state.status === "EDIT") return null; + if(this.state.status === TableDb.TABLE_STATUS.ADD) return null; + if(this.state.status === TableDb.TABLE_STATUS.EDIT) return null; + + return "none"; + } + + if(type === "TABLE") + { + //if(this.state.status === "EDIT") return "none"; + if(this.state.status === TableDb.TABLE_STATUS.ADD) return "none"; + if(this.state.status === TableDb.TABLE_STATUS.EDIT) return "none"; + return null; + + //if(this.state.rowID > 0) return "none"; + //return null; + } + } + + setDense(dense) + { + this.dense = dense; + } + + setStatus(status) + { + this.setState({"status": status}); + } + + getStatus() + { + if(this.state.status === TableDb.TABLE_STATUS.EDIT || this.state.status === TableDb.TABLE_STATUS.ADD) return "EDIT"; + else return "SHOWTABLE"; + } + + renderForm() + { + return

Editacia riadku

; + } + + renderTable() + { + //console.log(this.state.data); + //alert("render table"); + return( + + ) + } + + renderUiForm(status) + { + + let form = this.renderForm(); + + let txt = "editácia"; + if(this.state.status === TableDb.TABLE_STATUS.ADD) txt = "nový záznam"; + //TODO set vytvorenie + + return ( + + + + + + + + + + + +
+ {form} +
+
+
+ ) + } + + renderUiTable() + { + return( + + + + + + {this.renderCancelFilterIcon()} + + +
+ {this.renderTable()} +
+
+
+ ) + } + + renderUi() + { + let status = this.getStatus(); + let form = null; + + //add or edit record + if(status !== TableDb.TABLE_STATUS.SHOWTABLE) + { + form = this.renderUiForm(); + } + + return( + <> +
+ {form} +
+ +
+ {this.renderUiTable()} +
+ + ) + } + + render() { + + let findDialog = null; + + if(this.state.showFindDialog) + { + findDialog = this.renderFindDialog(); + } + + return ( +
+ {this.renderUi()} + {this.renderMainMenu()} + {findDialog} + {this.renderAlertDialog()} +
+ ) + } + + componentDidMount() + { + this.showTable(); + } + + renderCancelFilterIcon() + { + if (this.state.showFilter) return ; + return null; + } + + handleCancelFilter() + { + //let newState = {...this.state}; + //newState.showFilter = false; + //this.setState(newState); + //this.state.showFilter = false; + + this.setState(prevstate => ({ showFilter: false})); + + this.showTable(); + } + + setRow(row) + { + + } + + showTable(sqlQueryBuilder) + { + + if(sqlQueryBuilder === undefined) sqlQueryBuilder = this.getDefaultQueryBuilder(); + + //!force to show progress + this.setState(prevstate => ({ showFilter: prevstate.showFilter, data: null})); + + this.db.dbSelect(sqlQueryBuilder, true).then( + result => { + result = this.db.updateJsonTypes(result, this.getTableName(true)); + //console.log(result); + + //modify data + for(let i = 0; i < result.length; ++i) + { + this.setRow(result[i]); + } + + let newState = {...this.state}; + newState.data = result; + this.setState(newState); + //this.showAlertDialog("Info", "Data boli načítané") + }, + error => { + //alert(error); + this.showAlertDialog("Chyba", error); + } + ); + } + + getTableName(withView = false) + { + return this.dbTable; + } + + getDefaultQueryBuilder() + { + + let table = this.getTableName(true); + if(table === "") new Error("table name or view is not specified"); + + let sqlQueryBuilder = new SqlQueryBuilder(); + sqlQueryBuilder.from( table ); + + return sqlQueryBuilder; + } + + buildFindSqlQuery(findDialogParams) + { + if(_.isEmpty(findDialogParams)) + { + this.setState(prevstate => ({ showFindDialog: false, showFilter: false})); + return null; + } + + this.setState(prevstate => ({ showFindDialog: false, showFilter: true})); + + return this.getDefaultQueryBuilder(); + } + + handleToggleMenu() + { + this.setState(prevstate => ({ openMenu: true})); + } + + //run edit mode + handleTableDoubleClick(event, id, index){ + console.log("handleTableDoubleClick:", id, index); + + this.id = id; + this.index = index; + + this.setStatus(TableDb.TABLE_STATUS.EDIT); + + //this.setState(prevstate => ({ rowID: id, index: index})); + } + + handleTableClick(selectedRows){ + //console.log("tableDb::handleTableClick", selectedRows); + this.selectedRows = selectedRows; + }; + + + handleTableDeleteClick(event, id, index){ + + this.id = id; + this.index = index; + + this.menuClicked("delete"); + } + + + //end of AddRecord/EditRecord + handleBack(params){ + + //TODO refactor it + + //console.log(params, this.selectedRows); + + if(params === undefined) + { + this.setState({"status": TableDb.TABLE_STATUS.SHOWTABLE}); + return; + } + + let index = this.index; + //if(this.selectedRows.length === 1) index = this.selectedRows[0]; + + if(index === undefined || index === null) + { + for(let i = 0; i < this.state.data.length; i++) + { + if(this.state.data[i]["id"] === this.id) + { + index = i; + break; + } + } + } + + //TODO clone??? + let newState = this.state;//{...this.state}; + //let newState = {}; + newState.status = TableDb.TABLE_STATUS.SHOWTABLE; +// + //update current row; + if(index !== null) + { + //newState.data = {...this.state.data[index]}; + for (var prop in params) { + newState.data[index][prop] = params[prop]; + } + } + else + { + //console.log("handle back - index is null"); + + //TODO fix get last element and it's _index!!!!! + var last_element = this.state.data[this.state.data.length - 1]; + if(last_element === undefined) + { + //WHAT + //alert("tableDb.js fix"); + } + else{ + params["_index"] = last_element._index + 1; + newState.data.push(params); + } + + + + newState.status = TableDb.TABLE_STATUS.SHOWTABLE; + } + + this.setState(newState); + + } + + //reimplement it + renderMenu() + { + return null; + } + + renderMainMenu() + { + if(this.anchorMenuRef == null) return null; + if(this.anchorMenuRef.current == null) return null; + if(!this.state.openMenu) return null; + + return this.renderMenu(); + } + + renderFindDialog() + { + return null; + } + + renderAlertDialog() + { + return ( + + ) + } + + hideAlertDialog() + { + this.setState({showAlert: false}); + } + + showAlertDialog(title, message, type) + { + if(type === undefined) type = "warning"; + this.setState(prevstate => ({showAlert: true, alertTitle: title, alertContent: message, alertType: type})); + } + + menuClicked(menu) + { + this.menuClosed(); + + if(menu === "add") + { + this.index = null; + this.id = null; + + this.setState(prevstate => ({ + openMenu: false, + index: null, + status: TableDb.TABLE_STATUS.ADD + } + )); + + + } + else if(menu === "find") + { + this.setState(prevstate => ({ showFindDialog: true})); + return true; + } + else if(menu === "edit") + { + if(this.selectedRows.length === 0) + { + this.showAlertDialog("Editácia", "Nie sú označené žiadne riadky"); + return; + } + else if(this.selectedRows.length > 1) + { + this.showAlertDialog("Editácia", "Označte jeden riadkok"); + return; + } + + let index = this.selectedRows[0]; + if(index === undefined) + { + alert("menuClicked index is undefined"); + return; + } + + this.id = this.state.data[index].id; + this.index = index; + + this.setStatus(TableDb.TABLE_STATUS.EDIT); + } + else if(menu === "delete") + { + if(this.selectedRows.length === 0) + { + this.showAlertDialog("Mazanie", "Nie sú označené žiadne riadky"); + } + + let data = cloneDeep(this.state.data); + data.splice(this.index, 1) + + //update _index!!! + for (let i = 0; i < data.length; i++) { + data[i]._index = i; + } + + //this.setState(prevstate => ({ data: this.state.data.splice(this.index, 1)})); + + this.setState({data: data}); + //this.showAlertDialog("Mazanie", "pripravujeme..."); + + return true; + + } + + return false; + } + + deleteRow(index) + { + let data = cloneDeep(this.state.data); + data.splice(index, 1) + + //update _index!!! + for (let i = 0; i < data.length; i++) { + data[i]._index = i; + } + + this.setState({data: data}); + } + + menuClosed() + { + let newState = this.state; + newState.openMenu = false; + + this.setState(newState); + + //this.setState(prevstate => ({ openMenu: false})); + }; + +} + +export default TableDb; \ No newline at end of file diff --git a/src/config/config.js b/src/config/config.js new file mode 100644 index 0000000..bdf9832 --- /dev/null +++ b/src/config/config.js @@ -0,0 +1,138 @@ +/* +0 - cislo registra / prikaz +1 - recepient - 0 = master, 1 = slave (slave je automaticky group a broadcast) +2 - r/rw - read/write +3- register name - nazov registra / prikazu (len pre info - zobrazenie v aplikacii) + +4,5,6,7, - jednotlive byte-y - nazov byte-u +4-7 - RES. a prazdny string "" sa nezobrazia!!! +*/ + +//124 zaznamov +export const config = [ + ["0","1","R","Status","","",""], + ["1","1","RW","Dimming","R-Channel ","G-Channel","B-Channel ","W - Channel"], + ["2","1","RW","Device types","","","",""], + ["3","1","RW","Group addresses 1-4","Groups Add. 4","Groups Add. 3","Groups Add. 2","Groups Add. 1"], + ["4","1","RW","Group addresses 5-8","Groups Add. 8","Groups Add. 7","Groups Add. 6","Groups Add. 5"], + ["5","1","RW","Serial number (MAC)","","","",""], + ["6","1","RW","Time of dusk","HH","MM","SS","EXTRA"], + ["7","1","RW","Time of dawn","HH","MM","SS","EXTRA"], + ["8","1","RW","Time Schedule settings","TBD","TBD","Movement sensor","Time Schedule"], + ["9","1","RW","TS1 Time point 1","HH","MM","SS","Ext. Device"], + ["10","1","RW","TS1 Time point 1 Levels","R-Channel","G-Channel","B-Channel","W - Channel"], + ["11","1","RW","TS1 Time point 2","HH","MM","SS","Ext. Device"], + ["12","1","RW","TS1 Time point 2 Levels","R-Channel","G-Channel","B-Channel","W - Channel"], + ["13","1","RW","TS1 Time point 3","HH","MM","SS","Ext. Device"], + ["14","1","RW","TS1 Time point 3 Levels","R-Channel","G-Channel","B-Channel","W - Channel"], + ["15","1","RW","TS1 Time point 4","HH","MM","SS","Ext. Device"], + ["16","1","RW","TS1 Time point 4 Levels","R-Channel","G-Channel","B-Channel","W - Channel"], + ["17","1","RW","TS1 Time point 5","HH","MM","SS","Ext. Device"], + ["18","1","RW","TS1 Time point 5 Levels","R-Channel","G-Channel","B-Channel","W - Channel"], + ["19","1","RW","TS1 Time point 6","HH","MM","SS","Ext. Device"], + ["20","1","RW","TS1 Time point 6 Levels","R-Channel","G-Channel","B-Channel","W - Channel"], + ["21","1","RW","TS1 Time point 7","HH","MM","SS","Ext. Device"], + ["22","1","RW","TS1 Time point 7 Levels","R-Channel","G-Channel","B-Channel","W - Channel"], + ["23","1","RW","TS1 Time point 8","HH","MM","SS","Ext. Device"], + ["24","1","RW","TS1 Time point 8 Levels","R-Channel","G-Channel","B-Channel","W - Channel"], + ["25","1","RW","TS1 Time point 9","HH","MM","SS","Ext. Device"], + ["26","1","RW","TS1 Time point 9 Levels","R-Channel","G-Channel","B-Channel","W - Channel"], + ["27","1","RW","TS1 Time point 10","HH","MM","SS","Ext. Device"], + ["28","1","RW","TS1 Time point 10 Levels","R-Channel","G-Channel","B-Channel","W - Channel"], + ["29","1","RW","TS1 Time point 11","HH","MM","SS","Ext. Device"], + ["30","1","RW","TS1 Time point 11 Levels","R-Channel","G-Channel","B-Channel","W - Channel"], + ["31","1","RW","TS1 Time point 12","HH","MM","SS","Ext. Device"], + ["32","1","RW","TS1 Time point 12 Levels","R-Channel","G-Channel","B-Channel","W - Channel"], + ["33","1","RW","TS1 Time point 13","HH","MM","SS","Ext. Device"], + ["34","1","RW","TS1 Time point 13 Levels","R-Channel","G-Channel","B-Channel","W - Channel"], + ["35","1","RW","TS1 Time point 14","HH","MM","SS","Ext. Device"], + ["36","1","RW","TS1 Time point 14 Levels","R-Channel","G-Channel","B-Channel","W - Channel"], + ["37","1","RW","TS1 Time point 15","HH","MM","SS","Ext. Device"], + ["38","1","RW","TS1 Time point 15 Levels","R-Channel","G-Channel","B-Channel","W - Channel"], + ["39","1","RW","TS1 Time point 16","HH","MM","SS","Ext. Device"], + ["40","1","RW","TS1 Time point 16 Levels","R-Channel","G-Channel","B-Channel","W - Channel"], + ["41","1","RW","TS2 Time point 1","HH","MM","SS","Ext. Device"], + ["42","1","RW","TS2 Time point 1 Levels","R-Channel","G-Channel","B-Channel","W - Channel"], + ["43","1","RW","TS2 Time point 2","HH","MM","SS","Ext. Device"], + ["44","1","RW","TS2 Time point 2 Levels","R-Channel","G-Channel","B-Channel","W - Channel"], + ["45","1","RW","TS2 Time point 3","HH","MM","SS","Ext. Device"], + ["46","1","RW","TS2 Time point 3 Levels","R-Channel","G-Channel","B-Channel","W - Channel"], + ["47","1","RW","TS2 Time point 4","HH","MM","SS","Ext. Device"], + ["48","1","RW","TS2 Time point 4 Levels","R-Channel","G-Channel","B-Channel","W - Channel"], + ["49","1","RW","TS2 Time point 5","HH","MM","SS","Ext. Device"], + ["50","1","RW","TS2 Time point 5 Levels","R-Channel","G-Channel","B-Channel","W - Channel"], + ["51","1","RW","TS2 Time point 6","HH","MM","SS","Ext. Device"], + ["52","1","RW","TS2 Time point 6 Levels","R-Channel","G-Channel","B-Channel","W - Channel"], + ["53","1","RW","TS2 Time point 7","HH","MM","SS","Ext. Device"], + ["54","1","RW","TS2 Time point 7 Levels","R-Channel","G-Channel","B-Channel","W - Channel"], + ["55","1","RW","TS2 Time point 8","HH","MM","SS","Ext. Device"], + ["56","1","RW","TS2 Time point 8 Levels","R-Channel","G-Channel","B-Channel","W - Channel"], + ["57","1","RW","TS2 Time point 9","HH","MM","SS","Ext. Device"], + ["58","1","RW","TS2 Time point 9 Levels","R-Channel","G-Channel","B-Channel","W - Channel"], + ["59","1","RW","TS2 Time point 10","HH","MM","SS","Ext. Device"], + ["60","1","RW","TS2 Time point 10 Levels","R-Channel","G-Channel","B-Channel","W - Channel"], + ["61","1","RW","TS2 Time point 11","HH","MM","SS","Ext. Device"], + ["62","1","RW","TS2 Time point 11 Levels","R-Channel","G-Channel","B-Channel","W - Channel"], + ["63","1","RW","TS2 Time point 12","HH","MM","SS","Ext. Device"], + ["64","1","RW","TS2 Time point 12 Levels","R-Channel","G-Channel","B-Channel","W - Channel"], + ["65","1","RW","TS2 Time point 13","HH","MM","SS","Ext. Device"], + ["66","1","RW","TS2 Time point 13 Levels","R-Channel","G-Channel","B-Channel","W - Channel"], + ["67","1","RW","TS2 Time point 14","HH","MM","SS","Ext. Device"], + ["68","1","RW","TS2 Time point 14 Levels","R-Channel","G-Channel","B-Channel","W - Channel"], + ["69","1","RW","TS2 Time point 15","HH","MM","SS","Ext. Device"], + ["70","1","RW","TS2 Time point 15 Levels","R-Channel","G-Channel","B-Channel","W - Channel"], + ["71","1","RW","TS2 Time point 16","HH","MM","SS","Ext. Device"], + ["72","1","RW","TS2 Time point 16 Levels","R-Channel","G-Channel","B-Channel","W - Channel"], + ["73","1","RW","Power meter status","TBD","","",""], + ["74","1","R","Input Voltage","","","",""], + ["75","1","R","Input Current","","","",""], + ["76","1","R","Input Power","","","",""], + ["77","1","R","Cos phi","","","",""], + ["78","1","R","Frequency","","","",""], + ["79","1","RW","Energy","","","",""], + ["80","1","RW","Lifetime","","","",""], + ["81","1","RW","Power on cycles (input)","","","",""], + ["82","1","RW","Power on cycles (relay)","","","",""], + ["83","1","R","Time since last power on","","","",""], + ["84","1","R","Accelerometer data","","","",""], + ["85","1","RW","GPS latitude","pos/neg","deg","min ","sec"], + ["86","1","RW","GPS longitude","pos/neg","deg","min ","sec"], + ["87","1","RW","Actual time","HH","MM","SS","RES."], + ["88","1","RW","Actual date","Day of week","Day","Month","Year"], + ["89","1","R","Production data 1","","","",""], + ["90","1","R","Production data 2","","","",""], + ["91","1","RW","Network ID","NID3","NID2","NID1","NID0"], + ["95","1","RW","Actual Lux level from cabinet","RES.","RES.","HB","LB"], + ["96","1","RW","Threshold lux level","Dusk HB","Dusk LB","Dawn HB","Dawn LB"], + ["97","1","RW","Adjust period","Dusk HB","Dusk LB","Dawn HB","Dawn LB"], + ["98","1","RW","Offset","RES.","RES.","Dusk","Dawn"], + ["99","1","RW","CCT min/max range","max-H","max-L","min-H","min-L"], + ["100","1","RW","DALI interface","Cmd ID","Add","Cmd","Resp"], + ["101","1","RW","Module FW ver","v1","v2","v3","v4"], + ["102","1","RW","Module MAC-H","unused","unused","M1","M2"], + ["103","1","RW","Module MAC-L","M3","M4","M5","M6"], + ["104","1","RW","Sensor timeout","","","Timeout H","Timeout L"], + ["122","1","R","FW update status/control register","Byte3","Byte2","Byte1","Byte0"], + ["123","1","R","FW update - data index","Byte3","Byte2","Byte1","Byte0"], + ["124","1","R","FW update - data","Byte3","Byte2","Byte1","Byte0"], + ["0","0","R","Status","","","",""], + ["1","0","RW","Control register","RES.","RES.","RES.","init mode enable"], + ["2","0","R","Device types","","","",""], + ["3","0","R","Serial number (MAC)","","","",""], + ["4","0","R","Production data 1","","","",""], + ["5","0","R","Production data 2","","","",""], + ["6","0","RW","Network ID","NID3","NID2","NID1","NID0"], + ["7","0","RW","RS232 settings","param.","param.","Baudrate H","Baudrate L"], + ["8","0","R","Accelerometer data","","","",""], + ["9","0","RW","Module FW ver","v1","v2","v3","v4"], + ["10","0","RW","Module MAC-H","unused","unused","M1","M2"], + ["11","0","RW","Module MAC-L","M3","M4","M5","M6"], + ["32","0","RW","FW update status/control register","Byte3","Byte2","Byte1","Byte0"], + ["33","0","RW","FW update - data index","Byte3","Byte2","Byte1","Byte0"], + ["34","0","RW","FW update - data","Byte3","Byte2","Byte1","Byte0"], + ["125","0","RW","Debug Register","Byte3","Byte2","Byte1","Byte0"], + ["126","0","RW","Network Control Register","Byte3","Byte2","Byte1","Byte0"], + ["127","0","R","Network Status Register","Byte3","Byte2","Byte1","Byte0"], + ["128","0","RW","Node XX Serial Number Register","SER3","SER2","SER1","SER0"], + ["256","0","R","Node XX Network Status Register","","","",""] +]; \ No newline at end of file diff --git a/src/controls.js b/src/controls.js new file mode 100644 index 0000000..88eb04d --- /dev/null +++ b/src/controls.js @@ -0,0 +1,770 @@ +import React, {Component} from 'react' + +import Grid from '@material-ui/core/Grid'; +import Select from '@material-ui/core/Select'; +import InputLabel from '@material-ui/core/InputLabel'; +import MenuItem from '@material-ui/core/MenuItem'; +import Button from '@material-ui/core/Button'; +import Card from '@material-ui/core/Card'; +import CardActions from '@material-ui/core/CardActions'; +import CardContent from '@material-ui/core/CardContent'; +import Typography from '@material-ui/core/Typography'; +import TextField from '@material-ui/core/TextField'; + +import IconButton from '@material-ui/core/IconButton'; +import Menu from '@material-ui/core/Menu'; +import MoreVertIcon from '@material-ui/icons/MoreVert'; + +import axios from 'axios'; + + +import { crc8, crc16, crc32 } from 'easy-crc'; + +import {connect} from 'react-redux' +import * as actionTypes from './store/actions'; + +import * as data from './config/config'; + +//axios.defaults.baseURL = 'http://localhost:5000/'; + +function getParams() +{ + let params = {}; + + //core rpc values + params.address = 0;//if(recipient === 0) address = 0; + params.byte1 = 0; + params.byte2 = 0; + params.byte3 = 0; + params.byte4 = 0; + params.recipient = 0;//0: Master, 1: Slave, 2: Broadcast + params.register = 0;//register number + params.rw = 0;//0: read, 1: write + + //other values + //params.type = "rpc"; + //params.tbname = tbname; + //params.timestamp = 0;//execution time + //params.addMinutesToTimestamp = 0;//repeat if > 0, + //params.debug = ""; + + return params; +} + +class Controls extends Component { + constructor(props) { + super(props); + + + //default values + let tasks = this.buildTasks("R", 0); + let registerNumber = tasks[0].registerNumber; + + this.state = { + rw: "R", + recipient: 0, + registerNumber: registerNumber, + index: 0, + + anchorEl: null, + + requestToHost: "", + responsefromHost: "", + tasks: tasks, + showAddress: false, + address: "", + byte1Text: "", + byte2Text: "", + byte3Text: "", + byte4Text: "", + byte1: "", + byte2: "", + byte3: "", + byte4: "" + } + } + + checkForm = () =>{ + + let errors = []; + if(this.state.showAddress) + { + if(this.state.address === "") + { + errors.push("Address can not be empty"); + } + else if (!Number.isInteger( parseInt(this.state.address) )) { + errors.push("Address is not integer"); + } + } + + if(errors.length > 0) + { + alert(errors.join("\n")); + return false; + } + + return true; + } + + //test + handleResponse = (bytes) => + { + //local add = bit.bor(bit.lshift(bytes[1], 24), bit.lshift(bytes[2], 16), bit.lshift(bytes[3], 8), bytes[4]); + + //https://www.w3schools.com/js/js_bitwise.asp + + let type = "RESPONSE"; + if(bytes[4] === 0) type = "RESPONSE"; + else if(bytes[4] === 1) type = "ERROR"; + else if(bytes[4] === 2) type = "EVENT"; + else type = "UNKNOWN"; + + let crc = crc16('ARC', bytes.slice(0, 9)); + let c1 = (crc >> 8) & 0xFF; + let c2 = crc & 0xFF; + + + console.log("crc", bytes, c1, c2); + + //let add = (bytes[1] >> 24) + } + + //test + com_generic = (adresa, rec, rw, register, name, byte1, byte2, byte3, byte4) =>{ + let resp = []; + + let cmd = register; + + if (rw === 0) + { + cmd = cmd + 0x8000; + } + + if (rec === 3) + { + resp.push(0xFF); + resp.push(0xFF); + resp.push(0xFF); + resp.push(0xFF); + resp.push( adresa & 0xFF );//band + } + else + { + resp.push( (adresa >> 24) & 0xFF);//rshift + resp.push( (adresa >> 16) & 0xFF); + resp.push( (adresa >> 8) & 0xFF); + resp.push( adresa & 0xFF ); + + if (rec === 2) + { + resp.push(0xFF); + } + else resp.push(0); + } + + resp.push( (cmd >> 8) & 0xFF);//rshift + resp.push( cmd & 0xFF );//band + resp.push( byte1 & 0xFF );//band + resp.push( byte2 & 0xFF );//band + resp.push( byte3 & 0xFF );//band + resp.push( byte4 & 0xFF );//band + + //let data = '12345'; + let crc = crc16('ARC', resp); + let c1 = (crc >> 8) & 0xFF; + let c2 = crc & 0xFF; + + resp.push(c1); + resp.push(c2); + + + console.log("checksum", crc); + console.log("resp", resp); + + return resp; + } + + //send DATA + handleClick = event => { + + //this.com_generic(100, 1, 80, 'Control register', 0, 0, 0, 0);return; + + //let bytes = [0,0,0,0,0,128,1,0,0,0,0]; + //this.handleResponse(bytes); + //return; + + if(!this.checkForm()) + { + return; + } + + const found = this.state.tasks.find(element => element.registerNumber === this.state.registerNumber); + if(found === undefined) + { + alert("Invalid register number: " + this.state.registerNumber); + return; + } + + let address = parseInt(this.state.address); + if (isNaN(address)) address = 0; + + let recipient = parseInt(this.state.recipient); + if (isNaN(recipient)) recipient = 0; + + //master + if(recipient === 0) address = 0; + + if(recipient === 2) + { + address = 0xffffffff;//Broadcast + } + + let byte1 = parseInt(this.state.byte1); + if (isNaN(byte1)) byte1 = 0; + + let byte2 = parseInt(this.state.byte2); + if (isNaN(byte2)) byte2 = 0; + + let byte3 = parseInt(this.state.byte3); + if (isNaN(byte3)) byte3 = 0; + + let byte4 = parseInt(this.state.byte4); + if (isNaN(byte4)) byte4 = 0; + + let register = parseInt(this.state.registerNumber); + + //Zadaný index potom prirátaš k registru 128. Takže napríklad pri indexe 1 sa vyčítava z registra 129. Pri indexe 20, by sa vyčítalo z registra 148.. + if(this.state.rw === "R" && this.state.registerNumber === "128") + { + register = 128 + parseInt(this.state.index); + } + + let rw = 0;//read + if(this.state.rw === "W") rw = 1; + + let cmd = register; + if (rw === 0) + { + //cmd = cmd + 0x8000; + } + + let paramsToSend = { + command: { + register: cmd, + name: found.registerName, + recipient: recipient, + rw: rw, + address: address, + byte1: byte1, + byte2: byte2, + byte3: byte3, + byte4: byte4, + }, + username: this.props.username, + password: this.props.password, + }; + + //adresa, rec, cmd, name, byte3, byte2, byte1, byte0 + console.log(paramsToSend.command); + + //com generic is modifying cmd!!! use only for debug / info + let result = this.com_generic( + paramsToSend.command.address, + paramsToSend.command.recipient, + paramsToSend.command.rw, + paramsToSend.command.register, + 'Control register', + byte1, + byte2, + byte3, + byte4 + ); + + + let registerNames = [];// = + " from " ;// + " (WRITE: " + result.join(", ") + ")"; + registerNames.push(found.registerName); + if (recipient === 0) + { + registerNames.push("from"); + registerNames.push("Master"); + } + + if (recipient === 1) + { + registerNames.push("from"); + //let slave = 2*256 + result[3]; + let slave = address; + + registerNames.push( slave ); + } + + registerNames.push( "(WRITE: " + result.join(", ") + ")" ); + + let registerName = registerNames.join(" "); + + let params = { + command: { + //registerNumber: this.state.registerNumber, + //registerName: found.registerName + " (WRITE: " + result.join(", ") + ")", + registerName: registerName, + direction: "in", + type: "command", + date: new Date() + } + } + + //co zapisujeme? + this.props.addCommand(params); + + let url = '/cmd'; + //if(window.location.hostname === "localhost") url = "http://localhost:5000/cmd"; + if(window.location.hostname === "localhost") + { + //url = "http://localhost:5000/cmd"; + url = "http://10.0.0.5:5000/cmd"; + } + + this.setState({ + requestToHost: url + }); + + console.log(url); + + //make http request + axios(url, + { + method: 'POST', + mode: 'no-cors', + data: paramsToSend, + headers: { + 'Access-Control-Allow-Origin': '*', + 'Content-Type': 'application/json', + //"Content-Type": "text/html;charset=UTF-8" + }, + withCredentials: false, + credentials: 'same-origin', + }) + .then(response => { + //HANDLE response + console.log("RESPONSE", response.data.messages); + //alert(response.data.hostname); + + //multiple messages + if(Array.isArray(response.data.messages)) + { + + for(let i = 0; i < response.data.messages.length; i++) + { + let date = null; + let m = response.data.messages[i]; + let message; + let type; + + if(typeof m === 'object') + { + if ("date" in m) + { + date = new Date(m.date); + } + + message = m.message; + type = m.type; + + if(typeof message === 'object') + { + message = message.message; + type = message.type; + } + + + } + else + { + message = m; + } + + let params = { + command: { + responsefromHost: response.data.hostname, + direction: "out", + type: type, + message: message, + date: date + } + } + + this.props.addCommand(params); + + let hostInfo = response.data.hostname; + if(hostInfo === "localhost") hostInfo = {hostInfo} + else if(hostInfo !== "localhost") hostInfo = {hostInfo} + + this.setState({ + responsefromHost: hostInfo + }); + } + + return; + } + + + let params = { + command: { + direction: "out", + type: response.data.type, + message: response.data.message, + date: new Date() + } + } + + this.props.addCommand(params); + + }) + .catch(error => { + + //http request err + const message = error + ""; + + let params = { + command: { + direction: "out", + type: "error", + message: message, + date: new Date() + } + } + + this.props.addCommand(params); + + }); + }; + + handleClear = event => { + this.props.clearState(); + }; + + buildTasks = (rw, recipient) =>{ + + let tasks = []; + for (let i = 0; i < data.config.length; i++) { + + let registerNumber = data.config[i][0]; + let masterSlaveFlag = data.config[i][1]; + let rwFlag = data.config[i][2]; + let registerName = data.config[i][3]; + + /* + if (rwFlag.includes(rw)) { + if (recipient === masterSlaveFlag){ + tasks.push({registerNumber: registerNumber, registerName: registerName}); + } + } + */ + + + if (rwFlag.includes(rw)) { + //master + if (recipient === 0){ + if (masterSlaveFlag === "0"){ + tasks.push({registerNumber: registerNumber, registerName: registerName}); + } + } + else{ + //slave / Broadcast 1, 2 + if (masterSlaveFlag === "1"){ + tasks.push({registerNumber: registerNumber, registerName: registerName}); + } + } + } + + } + + return tasks; + } + + + menuItemSelected = item =>{ + this.props.handleCommand(item); + this.handleMenuClose(); + } + + handleMenuClose = event =>{ + this.setState({ + anchorEl: null + }); + } + + handleMenuClick = event =>{ + //this.anchorEl = event.currentTarget; + + this.setState({ + anchorEl: event.currentTarget + }); + } + + handleTextFieldChange = event =>{ + this.setState({ + [event.target.name]: event.target.value + }); + } + + handleChange = event => { + + let name = event.target.name; + let value = event.target.value; + + let rw = this.state.rw; + let recipient = this.state.recipient; + let registerNumber = this.state.registerNumber; + + let obj = { + ...this.state, + [name]: value, + } + + if(name === "rw") rw = value; + if(name === "registerNumber") registerNumber = value; + if(name === "recipient") + { + //slave + if(value === 1) obj.showAddress = true; + else obj.showAddress = false; + + recipient = value; + } + + let tasks = this.buildTasks(rw, recipient); + + if(name !== "registerNumber") + { + //tasks was rebuil, set first element + console.log(tasks); + + if(tasks.length > 0) + { + registerNumber = tasks[0].registerNumber; + obj.registerNumber = registerNumber; + } + + } + + //clear bit values and set labels + if(this.state.registerNumber !== registerNumber) + { + + for(let i = 0; i < 4; i++) + { + let key = "byte" + (i + 1); + + obj[key] = ""; + obj[key + "Text"] = ""; + } + + const found = data.config.find(element => element[0] === registerNumber); + if(found && rw === "W") + { + var bytes = found.slice(4, 8); + + for(let i = 0; i < bytes.length; i++) + { + let key = "byte" + (i + 1); + let value = bytes[i]; + + if(value === "RES.") value = ""; + + obj[key] = ""; + obj[key + "Text"] = value; + } + } + } + + //console.log(obj); + + obj.tasks = tasks; + + this.setState(obj); + }; + + render() { + + let MenuItemTasks = []; + for (var i = 0; i < this.state.tasks.length; i++) { + MenuItemTasks.push( {this.state.tasks[i].registerName} ); + } + + let address; + if(this.state.showAddress) + { + address = ; + } + + let bytes = []; + const found = this.state.tasks.find(element => element.registerNumber === this.state.registerNumber); + if(found !== undefined && this.state.rw === "W") + { + for(i = 0; i < 4; i++) + { + let key = "byte" + (i + 1); + + //byte1 MSB = data3, byte2 = data2, byte3 = data1, byte4 = data0 LSB + + let byteDescription = "byte" + (i + 1); + if(i === 0) byteDescription = byteDescription + " (MSB)"; + if(i === 3) byteDescription = byteDescription + " (LSB)"; + + let str = this.state[key + "Text"] + ": " + byteDescription; + + if(str !== "") + { + let byte = ; + bytes.push({byte}); + } + + } + } + + let index; + //Node XX Serial Number Register + //console.log(this.state.registerNumber); + if(this.state.rw === "R" && this.state.registerNumber === "128") + { + let key = "index"; + let str = "Index"; + //console.log(this.state.registerNumber); + index = ; + index = {index} + } + + return ( +
{ this.divElement = divElement } }> + + + + + + + + + + this.menuItemSelected('settings')}>Nastavenia + Logout + + + + + {this.props.title} + + + + + + + + <> + + + Read/Write + + + + Recip. + + + + Task + + + + + Request to: {this.state.requestToHost}
response from: {this.state.responsefromHost} +
+ + {bytes} + + {index} + + + {address} + +
+ +
+ + + + + +
+
+ ); + } +} + +const mapDispatchToProps = dispatch => { + return { + addCommand: (params) => dispatch ({ + type: actionTypes.ADD_COMMAND, + payload: { + command: params.command + } + }), + clearState: (params) => dispatch ({ + type: actionTypes.CLEAR_STATE, + }) + } + } + + + export default connect(null, mapDispatchToProps)(Controls); \ No newline at end of file diff --git a/src/index.css b/src/index.css new file mode 100644 index 0000000..ec2585e --- /dev/null +++ b/src/index.css @@ -0,0 +1,13 @@ +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +code { + font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', + monospace; +} diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..7bc4e03 --- /dev/null +++ b/src/index.js @@ -0,0 +1,22 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import './index.css'; +import App from './App'; +import reportWebVitals from './reportWebVitals'; + +import { createStore } from 'redux'; +import { Provider } from 'react-redux' +import reducer from './store/reducer' +const store = createStore(reducer); + +ReactDOM.render( + + + , + document.getElementById('root') +); + +// If you want to start measuring performance in your app, pass a function +// to log results (for example: reportWebVitals(console.log)) +// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals +reportWebVitals(); diff --git a/src/logo.svg b/src/logo.svg new file mode 100644 index 0000000..9dfc1c0 --- /dev/null +++ b/src/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/output.js b/src/output.js new file mode 100644 index 0000000..130054e --- /dev/null +++ b/src/output.js @@ -0,0 +1,108 @@ +import React, {Component} from 'react' +import {connect} from 'react-redux' +import { AnimatedList } from 'react-animated-list'; +import * as actionTypes from './store/actions' + +import moment from 'moment'; + +class Output extends Component { + + constructor(props) { + super(props); + this.state = { commands: [] } + + this.messagesEndRef = React.createRef(); + } + + componentDidMount () { + this.scrollToBottom() + } + componentDidUpdate () { + this.scrollToBottom() + } + scrollToBottom = () => { + this.messagesEndRef.current.scrollIntoView({ behavior: 'smooth' }) + } + + render() { + + let index = 0; + let responsefromHostWasShow = true;//obsolete + const items = this.props.commands.map(item => { + + let dateString = ""; + if("date" in item) + { + if(item.date != null) dateString = "[" + moment(item.date).format('DD.MM.YYYY hh.mm.ss') + "] "; + } + + if(item.direction === "in") + { + const container =
[{dateString}] COMMAND: {item.registerName}
; + + index++; + + return container; + } + else if (item.direction === "out") + { + //RESPONSE, ERROR, EVENT, UNKNOWN + + let color = '#95FE35';//RESPONSE + if(item.type === "ERROR") color = 'red'; + + let responsefromHost; + if(!responsefromHostWasShow) + { + responsefromHost = (
RESPONSE FROM HOST: {item.responsefromHost}
); + responsefromHostWasShow = true; + } + + const container = ( + <> + {responsefromHost} +
+ {dateString} + RESPONSE: {item.message} +
+ + ); + + index++; + + return container; + } + }) + + return ( + <> + + {items} + +
+ + ); + } +} + +const mapDispatchToProps = dispatch => { + return { + addCommand: (params) => dispatch ({ + type: actionTypes.ADD_COMMAND, + payload: { + command: params.command + } + }), + clearState: (params) => dispatch ({ + type: actionTypes.CLEAR_STATE, + }) + } + } + +const mapStateToProps = state => { + return{ + commands: state.commands, + } +}; + +export default connect(mapStateToProps, mapDispatchToProps)(Output); \ No newline at end of file diff --git a/src/reportWebVitals.js b/src/reportWebVitals.js new file mode 100644 index 0000000..5253d3a --- /dev/null +++ b/src/reportWebVitals.js @@ -0,0 +1,13 @@ +const reportWebVitals = onPerfEntry => { + if (onPerfEntry && onPerfEntry instanceof Function) { + import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { + getCLS(onPerfEntry); + getFID(onPerfEntry); + getFCP(onPerfEntry); + getLCP(onPerfEntry); + getTTFB(onPerfEntry); + }); + } +}; + +export default reportWebVitals; diff --git a/src/setupTests.js b/src/setupTests.js new file mode 100644 index 0000000..8f2609b --- /dev/null +++ b/src/setupTests.js @@ -0,0 +1,5 @@ +// jest-dom adds custom jest matchers for asserting on DOM nodes. +// allows you to do things like: +// expect(element).toHaveTextContent(/react/i) +// learn more: https://github.com/testing-library/jest-dom +import '@testing-library/jest-dom'; diff --git a/src/store/actions.js b/src/store/actions.js new file mode 100644 index 0000000..1703e55 --- /dev/null +++ b/src/store/actions.js @@ -0,0 +1,2 @@ +export const ADD_COMMAND = 'ADD_COMMAND'; +export const CLEAR_STATE = 'CLEAR_STATE'; \ No newline at end of file diff --git a/src/store/reducer.js b/src/store/reducer.js new file mode 100644 index 0000000..1560140 --- /dev/null +++ b/src/store/reducer.js @@ -0,0 +1,23 @@ +import * as actionTypes from './actions'; + +const initialState = { + commands: [], +} + +const reducer = (state = initialState, action) =>{ + //console.log(action.type, action.payload ); + if(action.type === actionTypes.ADD_COMMAND){ + return { + ...state, + commands: state.commands.concat(action.payload.command) + } + } + + if(action.type === actionTypes.CLEAR_STATE){ + return {commands: []}; + } + + return state; +}; + +export default reducer; \ No newline at end of file diff --git a/src/util/db/SqlQueryBuilder.js b/src/util/db/SqlQueryBuilder.js new file mode 100644 index 0000000..88e4748 --- /dev/null +++ b/src/util/db/SqlQueryBuilder.js @@ -0,0 +1,248 @@ + +export const LogicOperator = { + UndefOperator: '', + And: "AND", + Or: "OR" +} + +export const Function = { + UndefFn: 'UndefFn', + Min: "Min", + Max: "Max", + Avg: "Avg" +} + +class SqlQueryBuilder{ + + className = "SqlQueryBuilder"; + type = "SELECT"; + + queryData = { + columns: [], + tables: [], + wheres: [], + variables: {} + }; + + select(column, alias, fn) + { + /* + SqlQueryData::Column col; + col.column = column; + col.alias = alias; + col.func = fn; + + queryData.columns.append(col); + return this; + */ + + if(Array.isArray(column)) + { + for(let i = 0; i < column.length; i++) + { + let c = column[i]; + + let col; + let alias; + let fn; + + if(typeof c == "string") col = c; + if(col === undefined) continue; + + this.select(col, alias, fn); + } + + return; + } + + const SqlQueryData_Column = { + column: column, + alias: alias, + func: fn + }; + + this.queryData.columns.push(SqlQueryData_Column); + + return this; + + } + + from(table, alias) + { + + const SqlQueryData_Table = { + table: table, + alias: alias + }; + + this.queryData.tables.push(SqlQueryData_Table); + + return this; + } + + where(cond, op = LogicOperator.And) + { + //queryBuilderTmp->where("`date` >= :date1", SqlQueryBuilder::And); + if(LogicOperator === undefined) op = LogicOperator.And; + + const SqlQueryData_Where = { + cond: cond, + operation: op + }; + + this.queryData.wheres.push(SqlQueryData_Where); + } + + setParameter(key, value) + { + this.queryData.variables[key] = value; + } + + getSql() + { + let sql = this.type + " "; + + if(this.queryData.tables.length === 0) + { + throw "SqlQueryBuilder::queryData.tables.length === 0"; + } + + // blok stlpcov + if(this.type === "SELECT") + { + if(this.queryData.columns.length === 0) sql = sql + "*"; + } + + let params = []; + for(let i = 0; i < this.queryData.columns.length; i++) + { + let col = this.queryData.columns[i]; + + let column = col.column; + + params.push("`" + column + "`"); + } + + sql = sql + params.join(','); + + sql = sql + " FROM "; + + params = []; + for(let i = 0; i < this.queryData.tables.length; i++) + { + let table = this.queryData.tables[i]; + + params.push("`" + table.table + "`"); + } + + sql = sql + params.join(','); + + //WHERE + if(this.queryData.wheres.length > 0) + { + + let size = this.queryData.wheres.length; + + sql = sql + " WHERE "; + + for(let i = 0; i < size; i++) + { + let where = this.queryData.wheres[i]; + sql = sql + where.cond; + + if (where.operation !== LogicOperator.UndefOperator) + { + if(i < (size - 1)) + { + if (where.operation === LogicOperator.And) + { + sql = sql + " AND "; + } + else if (where.operation === LogicOperator.Or) + { + sql = sql + " OR "; + } + } + } + + } + } + + return sql; + } + + serializeToJson() + { + return JSON.stringify({ + className: "SqlQueryBuilder", + type: this.type, + queryData: this.queryData + }); + } + + //SqlQueryBuilder* select(const QString &column, const QString &alias = QString(), Function fn = UndefFn); + //SqlQueryBuilder* from(const QString &table, const QString &alias = QString()); + //SqlQueryBuilder* where(const QString &cond, LogicOperator op = UndefOperator); + //SqlQueryBuilder* join(const QString &joinWhat, const QString &cond = QString(), JoinType type = LeftJoin); + //SqlQueryBuilder* orderBy(const QString &name); + //SqlQueryBuilder* groupBy(const QString &name); + //SqlQueryBuilder* limit(int count); + //SqlQueryBuilder* limit(int from, int to); + //SqlQueryBuilder* ascend(Ascend asc = Ascending); + //SqlQueryBuilder* setParameters(const QMap ¶ms); + //void clearParameters(); + //QMap getParameters(); + //SqlQueryBuilder* setParameter(const QString &key, const QVariant &value); + //SqlQueryBuilder* removeParameter(const QString &key); + //void clear(); + //void clear(SqlPart); + //QString getSql(bool showError = true) const; + + /** + * \brief Query data storage, + */ + + /* + struct SqlQueryData + { + SqlQueryData() = default; + SqlQueryData(const SqlQueryData& other) + { + hasLimit = other.hasLimit; + asc = other.asc; + columns = other.columns; + tables = other.tables; + wheres = other.wheres; + joins = other.joins; + orderByes = other.orderByes; + groupByes = other.groupByes; + connectionName = other.connectionName; + variables = other.variables; + } + + struct Column { QString column, alias; SqlQueryBuilder::Function func; }; + struct Table { QString table, alias; }; + struct Join { SqlQueryBuilder::JoinType type; QString cond, what; }; + struct Where { QString cond; SqlQueryBuilder::LogicOperator operation; }; + + struct Limit { bool range; int from, to, count; } limit; + bool hasLimit = false; + + SqlQueryBuilder::Ascend asc; + + QList columns; + QList tables; + QList wheres; + QList joins; + QStringList orderByes; + QStringList groupByes; + + QString connectionName = ""; + + QMap variables; + } queryData; + */ + +} + +export default SqlQueryBuilder; \ No newline at end of file diff --git a/src/util/db/db.js b/src/util/db/db.js new file mode 100644 index 0000000..cbb66fe --- /dev/null +++ b/src/util/db/db.js @@ -0,0 +1,300 @@ +import axios from 'axios' +//import moment from 'moment' + +class DB{ + static token = ""; + static company = ""; + static connectionName = ""; + static db_structure = ""; + static backendUrl = '/backend/db_connector.php'; + + data = []; + transaction = false; + + updateJsonTypes(arr, table) + { + let table_structure; + if(DB.db_structure !== undefined) + { + table_structure = DB.db_structure[table]; + } + + let result = []; + let index = 0; + for(let i = 0; i < arr.length; ++i) + { + let record = arr[i]; + + + if(table_structure !== undefined) + { + let keys = Object.keys(record); + for(let ii = 0; ii < keys.length; ii++) + { + let key = keys[ii]; + if(table_structure[key] === undefined) + { + console.log("updateJsonTypes - undefined key", table, key); + continue; + } + + if(table_structure[key]["type"] === "uint" || table_structure[key]["type"] === "int") + { + record[key] = parseInt(record[key]); + } + else if(table_structure[key]["type"] === "double") + { + record[key] = parseFloat(record[key]); + } + else if(table_structure[key]["type"] === "QDateTime") + { + + } + else if(table_structure[key]["type"] === "char") + { + if(table_structure[key]["length"] === 1) + { + //boolean + let value = parseInt(record[key]); + //TODO use translation + if(value === 0) record[key] = "nie"; + else record[key] = "áno"; + } + } + else if(table_structure[key]["type"] === "QDate") + { + let d = new Date(record[key]); + record["_" + key] = record[key];//to preserve original value + //record[key] = moment(d).format('DD.MM.YYYY'); + } + + } + } + else + { + console.log("no table structure provided"); + } + + record._index = index; + result.push(record); + + index++ + } + + return arr; + } + + dbSelect(data, exec = false, error_message = "") + { + // + //let obj = {type: "select", error_message: error_message}; + //this.data.push(obj); + + //data can be string, or object SqlQueryBuilder!!! + //TODO push + + //or sql as string + if((typeof data) == "object") + { + if(data.className === "SqlQueryBuilder") + { + //if(error_message === undefined) error_message = "update pre tabulku " + table + " zlyhal"; + + //run later + if(exec === false) + { + let obj = {type: "select", className: "SqlQueryBuilder", obj: data, error_message: error_message}; + this.data.push(obj); + + return; + } + } + } + + if((typeof data) == "string") + { + //alert("str"); + } + + return new Promise((resolve, reject) => { + let obj = { + token: DB.token, + company: DB.company, + connectionName: DB.connectionName, + type: "select", + data: data, + error_message: error_message + } + + let json = JSON.stringify(obj); + + if(DB.backendUrl === undefined || DB.backendUrl === "") + { + reject("Nie je definovaný endpoint"); + } + + axios(DB.backendUrl, + { + method: 'POST', + mode: 'no-cors', + data: json, + headers: { + 'Access-Control-Allow-Origin': '*', + 'Content-Type': 'application/json', + }, + withCredentials: false, + credentials: 'same-origin', + }) + .then(response => { + + console.log(response); + + let success = response.data.success; + + if(!success) + { + //console.log(response.data); + let message = response.data.message; + if(message === undefined) message = "nedefinovaná chyba"; + + throw message; + } + + if(success) + { + resolve( response.data.result ); + } + + }) + .catch(error => { + const message = error + ""; + + reject(message); + }); + + }) + + } + + dbStartTransaction() + { + let obj = {type: "START TRANSACTION"}; + this.data.push(obj); + } + + dbCommitTransaction() + { + let obj = {type: "COMMIT"}; + this.data.push(obj); + } + + dbDelete(table, conditions, error_message) + { + + if(Number.isInteger(conditions)) + { + conditions = {"id": conditions}; + } + + if(error_message === undefined) error_message = "zlyhalo zmazanie z tabulky " + table; + let obj = {type: "delete", table: table, conditions: conditions, error_message: error_message}; + this.data.push(obj); + } + + dbInsert(data, table, foreign_keys, error_message) { + //console.log(data, table, condition); + +//foreign key: table: lastInsertedId +//foreign_keys: {"subject_id": {"subject": "id"}} + if(foreign_keys === undefined) foreign_keys = {}; + + //token + if(error_message === undefined) error_message = "insert pre tabulku " + table + " zlyhal"; + let obj = {type: "insert", foreign_keys: foreign_keys, table: table, params: {...data}, error_message: error_message}; + this.data.push(obj); + } + + dbUpdate(data, table, condition, error_message) { + //console.log(data, table, condition); + + //token + if(error_message === undefined) error_message = "update pre tabulku " + table + " zlyhal"; + let obj = {type: "update", table: table, params: {...data}, condition: condition, error_message: error_message}; + this.data.push(obj); + } + + exec() + { + //build json and send to server + + return new Promise((resolve, reject) => { + + let obj = { + token: DB.token, + company: DB.company, + connectionName: DB.connectionName, + data: this.data + } + + let json = JSON.stringify(obj); + + if(DB.backendUrl === undefined) + { + reject("Nie je definovaný endpoint"); + } + + axios(DB.backendUrl, + { + method: 'POST', + mode: 'no-cors', + data: json, + headers: { + 'Access-Control-Allow-Origin': '*', + "Content-Type": "text/html;charset=UTF-8" + }, + withCredentials: false, + credentials: 'same-origin', + }) + .then(response => { + + //console.log(response); + + if(!response.data.sucess) + { + //alert(response.data.error_message); + console.log(response); + + let errors = []; + if("data" in response) + { + if(Array.isArray(response.data.result)) + { + for(let i = 0; i < response.data.result.length; ++i) + { + if(!response.data.result[i].sucess) errors.push(response.data.result[i].error_message); + } + } + } + + throw errors; + } + + if(response.data.sucess) + { + resolve(response.data.result); + } + + }) + .catch(error => { + const message = error + ""; + + console.log("catsh", message, error); + + reject(message); + }); + + }) + } +} + +export default DB; \ No newline at end of file diff --git a/src/util/findGetParameterInUrl.js b/src/util/findGetParameterInUrl.js new file mode 100644 index 0000000..d900f7f --- /dev/null +++ b/src/util/findGetParameterInUrl.js @@ -0,0 +1,14 @@ +function findGetParameterInUrl(parameterName) { + var result = null, + tmp = []; + window.location.search + .substr(1) + .split("&") + .forEach(function (item) { + tmp = item.split("="); + if (tmp[0] === parameterName) result = decodeURIComponent(tmp[1]); + }); + return result; + } + +export default findGetParameterInUrl \ No newline at end of file diff --git a/src/util/isLocalhost.js b/src/util/isLocalhost.js new file mode 100644 index 0000000..e891e4d --- /dev/null +++ b/src/util/isLocalhost.js @@ -0,0 +1,11 @@ +const isLocalhost = Boolean( + window.location.hostname === 'localhost' || + // [::1] is the IPv6 localhost address. + window.location.hostname === '[::1]' || + // 127.0.0.0/8 are considered localhost for IPv4. + window.location.hostname.match( + /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ + ) + ); + +export default isLocalhost; \ No newline at end of file diff --git a/src/util/isMobile.js b/src/util/isMobile.js new file mode 100644 index 0000000..19a283a --- /dev/null +++ b/src/util/isMobile.js @@ -0,0 +1,15 @@ +function isMobile() +{ + var isMobile = false; //initiate as false + // device detection + if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|ipad|iris|kindle|Android|Silk|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(navigator.userAgent) + || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(navigator.userAgent.substr(0,4))) { + isMobile = true; + + return isMobile; + } + + return isMobile; +} + +export default isMobile; \ No newline at end of file