feat: add machine status page (#344)
* feat: add confirm-dialog component * feat: add MachineStatus to router * feat: add machine details to api endpoints * feat: add machine-status expandabletable * fix: add missing property to TextInput on story * style: minor style fixes * feat: useAxios to unpair and reboot specific machinees * fix: style fixes use shutdown instead of reboot use named colors * fix: use new ExpTable * fix: class instead of sttyles, use named colors * feat: use ConfirmDialog to confirm unpair action * chore: eslint fix * refactor: use gql, new ExpTable and ramda on machine-status * fix: 'fallback' status instead of the 'all good' one * fix: makeStyles instead of withStyles * refactor: simplify StatusChip * fix: css spacing instead of nbsp * fix: move makeStyles outside component * refactor: makeStyles instead of withStyles * refactor: adapting based props for Status * refactor: moar simple Status chip * feat: use graphql mutation instead of rest for machine action feat: use graphql instead of rest on MachineDetailsCard * fix: Dialog close must be handled outside * fix: just pass down onDissmissed and onConfirmed to the component https://github.com/lamassu/lamassu-server/pull/344#discussion_r370136028 * refactor: machineAction on separate file and 404 handling * feat: basic handling of graphql exceptions on machineAction
This commit is contained in:
parent
f1edea4e8a
commit
fdf18b60ad
14 changed files with 609 additions and 3 deletions
|
|
@ -15,6 +15,10 @@ function getMachines () {
|
|||
cashbox: r.cashbox,
|
||||
cassette1: r.cassette1,
|
||||
cassette2: r.cassette2,
|
||||
pairedAt: new Date(r.created).valueOf(),
|
||||
lastPing: new Date(r.last_online).valueOf(),
|
||||
// TODO: we shall start using this JSON field at some point
|
||||
// location: r.location,
|
||||
paired: r.paired
|
||||
})))
|
||||
}
|
||||
|
|
@ -32,8 +36,16 @@ function getMachineNames (config) {
|
|||
const machineScoped = configManager.machineScoped(r.deviceId, config)
|
||||
const name = _.defaultTo('', machineScoped.machineName)
|
||||
const cashOut = machineScoped.cashOutEnabled
|
||||
const machineModel = _.defaultTo('', machineScoped.machineModel)
|
||||
const machineLocation = _.defaultTo('', machineScoped.machineLocation)
|
||||
|
||||
return _.assign(r, {name, cashOut})
|
||||
// TODO: obtain next fields from somewhere
|
||||
const printer = null
|
||||
const pingTime = null
|
||||
const statuses = [{label: 'Unknown detailed status', type: 'warning'}]
|
||||
const softwareVersion = ''
|
||||
|
||||
return _.assign(r, {name, cashOut, machineModel, machineLocation, printer, pingTime, statuses, softwareVersion})
|
||||
}
|
||||
|
||||
return _.map(addName, machines)
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ const { GraphQLJSON, GraphQLJSONObject } = require('graphql-type-json')
|
|||
const got = require('got')
|
||||
|
||||
const machineLoader = require('../../machine-loader')
|
||||
const { machineAction } = require('../machines')
|
||||
const logs = require('../../logs')
|
||||
const supportLogs = require('../../support_logs')
|
||||
const settingsLoader = require('../../new-settings-loader')
|
||||
|
|
@ -42,6 +43,11 @@ const typeDefs = gql`
|
|||
display: String!
|
||||
}
|
||||
|
||||
type MachineStatus {
|
||||
label: String!
|
||||
type: String!
|
||||
}
|
||||
|
||||
type Machine {
|
||||
name: String!
|
||||
deviceId: ID!
|
||||
|
|
@ -49,6 +55,7 @@ const typeDefs = gql`
|
|||
cashbox: Int
|
||||
cassette1: Int
|
||||
cassette2: Int
|
||||
statuses: [MachineStatus]
|
||||
}
|
||||
|
||||
type Account {
|
||||
|
|
@ -154,7 +161,15 @@ const typeDefs = gql`
|
|||
deviceId: ID
|
||||
}
|
||||
|
||||
enum MachineAction {
|
||||
resetCashOutBills
|
||||
unpair
|
||||
reboot
|
||||
restartServices
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
machineAction(deviceId:ID!, action: MachineAction!): Machine
|
||||
machineSupportLogs(deviceId: ID!): SupportLogsResponse
|
||||
serverSupportLogs: SupportLogsResponse
|
||||
saveConfig(config: JSONObject): JSONObject
|
||||
|
|
@ -184,6 +199,7 @@ const resolvers = {
|
|||
config: () => settingsLoader.getConfig()
|
||||
},
|
||||
Mutation: {
|
||||
machineAction: (...[, { deviceId, action }]) => machineAction({ deviceId, action }),
|
||||
machineSupportLogs: (...[, { deviceId }]) => supportLogs.insert(deviceId),
|
||||
serverSupportLogs: () => serverLogs.insert(),
|
||||
saveConfig: (...[, { config }]) => settingsLoader.saveConfig(config)
|
||||
|
|
|
|||
20
lib/new-admin/machines.js
Normal file
20
lib/new-admin/machines.js
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
const machineLoader = require('../machine-loader')
|
||||
const { UserInputError } = require('apollo-server-express')
|
||||
|
||||
function getMachine(machineId) {
|
||||
return machineLoader.getMachines()
|
||||
.then(machines => machines.find(({ deviceId }) => deviceId === machineId))
|
||||
}
|
||||
|
||||
function machineAction({ deviceId, action }) {
|
||||
|
||||
return getMachine(deviceId)
|
||||
.then(machine => {
|
||||
if (!machine) throw new UserInputError(`machine:${deviceId} not found`, { deviceId })
|
||||
return machine
|
||||
})
|
||||
.then(machineLoader.setMachine({ deviceId, action }))
|
||||
.then(getMachine(deviceId))
|
||||
}
|
||||
|
||||
module.exports = { machineAction }
|
||||
5
new-lamassu-admin/public/icons/status/pumpkin.svg
Normal file
5
new-lamassu-admin/public/icons/status/pumpkin.svg
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>icon/status/warning</title>
|
||||
<rect width="12" height="12" rx="3" ry="3" fill="#ff7311"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 289 B |
5
new-lamassu-admin/public/icons/status/tomato.svg
Normal file
5
new-lamassu-admin/public/icons/status/tomato.svg
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>icon/status/warning</title>
|
||||
<rect width="12" height="12" rx="3" ry="3" fill="#ff584a"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 289 B |
96
new-lamassu-admin/src/components/ConfirmDialog.js
Normal file
96
new-lamassu-admin/src/components/ConfirmDialog.js
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
import {
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogTitle as MuiDialogTitle,
|
||||
IconButton,
|
||||
makeStyles
|
||||
} from '@material-ui/core'
|
||||
import React, { useState, memo } from 'react'
|
||||
|
||||
import { Button } from '../components/buttons'
|
||||
import { ReactComponent as CloseIcon } from '../styling/icons/action/close/zodiac.svg'
|
||||
import { spacer } from '../styling/variables'
|
||||
|
||||
import { TextInput } from './inputs'
|
||||
import { H4, P } from './typography'
|
||||
|
||||
const useStyles = makeStyles({
|
||||
closeButton: {
|
||||
position: 'absolute',
|
||||
right: spacer,
|
||||
top: spacer
|
||||
}
|
||||
})
|
||||
|
||||
export const DialogTitle = ({ children, onClose }) => {
|
||||
const classes = useStyles()
|
||||
return (
|
||||
<MuiDialogTitle>
|
||||
{children}
|
||||
{onClose && (
|
||||
<IconButton
|
||||
aria-label="close"
|
||||
className={classes.closeButton}
|
||||
onClick={onClose}>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
)}
|
||||
</MuiDialogTitle>
|
||||
)
|
||||
}
|
||||
|
||||
export const ConfirmDialog = memo(
|
||||
({
|
||||
title = 'Confirm action',
|
||||
subtitle = 'This action requires confirmation',
|
||||
open,
|
||||
toBeConfirmed,
|
||||
onConfirmed,
|
||||
onDissmised,
|
||||
...props
|
||||
}) => {
|
||||
const [value, setValue] = useState('')
|
||||
const handleChange = event => {
|
||||
setValue(event.target.value)
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={open} aria-labelledby="form-dialog-title" {...props}>
|
||||
<DialogTitle id="customized-dialog-title" onClose={onDissmised}>
|
||||
<H4>{title}</H4>
|
||||
{subtitle && (
|
||||
<DialogContentText>
|
||||
<P>{subtitle}</P>
|
||||
</DialogContentText>
|
||||
)}
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<TextInput
|
||||
label={`Write '${toBeConfirmed}' to confirm`}
|
||||
name="confirm-input"
|
||||
autoFocus
|
||||
id="confirm-input"
|
||||
type="text"
|
||||
large
|
||||
fullWidth
|
||||
value={value}
|
||||
touched={{}}
|
||||
error={toBeConfirmed !== value}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button
|
||||
color="green"
|
||||
disabled={toBeConfirmed !== value}
|
||||
onClick={onConfirmed}>
|
||||
Confirm
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
)
|
||||
69
new-lamassu-admin/src/components/Status.js
Normal file
69
new-lamassu-admin/src/components/Status.js
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
import React from 'react'
|
||||
import Chip from '@material-ui/core/Chip'
|
||||
import { makeStyles } from '@material-ui/core/styles'
|
||||
|
||||
import {
|
||||
tomato,
|
||||
mistyRose,
|
||||
pumpkin,
|
||||
secondaryColorDarker as spring4,
|
||||
inputFontWeight,
|
||||
spring3,
|
||||
smallestFontSize,
|
||||
inputFontFamily,
|
||||
spacer
|
||||
} from '../styling/variables'
|
||||
|
||||
const colors = {
|
||||
error: tomato,
|
||||
warning: pumpkin,
|
||||
success: spring4
|
||||
}
|
||||
|
||||
const backgroundColors = {
|
||||
error: mistyRose,
|
||||
warning: mistyRose,
|
||||
success: spring3
|
||||
}
|
||||
|
||||
const useStyles = makeStyles({
|
||||
root: {
|
||||
borderRadius: spacer / 2,
|
||||
marginTop: spacer / 2,
|
||||
marginRight: spacer / 4,
|
||||
marginBottom: spacer / 2,
|
||||
marginLeft: spacer / 4,
|
||||
height: 18,
|
||||
backgroundColor: ({ type }) => backgroundColors[type]
|
||||
},
|
||||
label: {
|
||||
fontSize: smallestFontSize,
|
||||
fontWeight: inputFontWeight,
|
||||
fontFamily: inputFontFamily,
|
||||
paddingRight: spacer / 2,
|
||||
paddingLeft: spacer / 2,
|
||||
color: ({ type }) => colors[type]
|
||||
}
|
||||
})
|
||||
|
||||
const Status = ({ status }) => {
|
||||
const classes = useStyles({ type: status.type })
|
||||
return <Chip type={status.type} label={status.label} classes={classes} />
|
||||
}
|
||||
|
||||
const MainStatus = ({ statuses }) => {
|
||||
const mainStatus =
|
||||
statuses.find(s => s.type === 'error') ||
|
||||
statuses.find(s => s.type === 'warning') ||
|
||||
statuses[0]
|
||||
const plus = { label: `+${statuses.length - 1}`, type: mainStatus.type }
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Status status={mainStatus} />
|
||||
{statuses.length > 1 && <Status status={plus} />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export { Status, MainStatus }
|
||||
242
new-lamassu-admin/src/pages/maintenance/MachineDetailsCard.js
Normal file
242
new-lamassu-admin/src/pages/maintenance/MachineDetailsCard.js
Normal file
|
|
@ -0,0 +1,242 @@
|
|||
import { makeStyles } from '@material-ui/core/styles'
|
||||
import classnames from 'classnames'
|
||||
import moment from 'moment'
|
||||
import React, { useState } from 'react'
|
||||
import { useMutation } from '@apollo/react-hooks'
|
||||
import { gql } from 'apollo-boost'
|
||||
import { Dialog, DialogContent } from '@material-ui/core'
|
||||
|
||||
import { H4 } from 'src/components/typography'
|
||||
|
||||
import ActionButton from '../../components/buttons/ActionButton'
|
||||
import { DialogTitle, ConfirmDialog } from '../../components/ConfirmDialog'
|
||||
import { Status } from '../../components/Status'
|
||||
import { ReactComponent as DownloadReversedIcon } from '../../styling/icons/button/download/white.svg'
|
||||
import { ReactComponent as DownloadIcon } from '../../styling/icons/button/download/zodiac.svg'
|
||||
import { ReactComponent as RebootReversedIcon } from '../../styling/icons/button/reboot/white.svg'
|
||||
import { ReactComponent as RebootIcon } from '../../styling/icons/button/reboot/zodiac.svg'
|
||||
import { ReactComponent as ShutdownReversedIcon } from '../../styling/icons/button/shut down/white.svg'
|
||||
import { ReactComponent as ShutdownIcon } from '../../styling/icons/button/shut down/zodiac.svg'
|
||||
import { ReactComponent as UnpairReversedIcon } from '../../styling/icons/button/unpair/white.svg'
|
||||
import { ReactComponent as UnpairIcon } from '../../styling/icons/button/unpair/zodiac.svg'
|
||||
import {
|
||||
detailsRowStyles,
|
||||
labelStyles
|
||||
} from '../Transactions/Transactions.styles'
|
||||
import { zircon } from '../../styling/variables'
|
||||
|
||||
const MACHINE_ACTION = gql`
|
||||
mutation MachineAction($deviceId: ID!, $action: MachineAction!) {
|
||||
machineAction(deviceId: $deviceId, action: $action) {
|
||||
deviceId
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const colDivider = {
|
||||
background: zircon,
|
||||
width: 2
|
||||
}
|
||||
|
||||
const inlineChip = {
|
||||
marginInlineEnd: '0.25em'
|
||||
}
|
||||
|
||||
const useLStyles = makeStyles(labelStyles)
|
||||
|
||||
const Label = ({ children }) => {
|
||||
const classes = useLStyles()
|
||||
|
||||
return <div className={classes.label}>{children}</div>
|
||||
}
|
||||
|
||||
const useMDStyles = makeStyles({ ...detailsRowStyles, colDivider, inlineChip })
|
||||
|
||||
const MachineDetailsRow = ({ it: machine }) => {
|
||||
const [errorDialog, setErrorDialog] = useState(false)
|
||||
const [dialogOpen, setOpen] = useState(false)
|
||||
const [actionMessage, setActionMessage] = useState(null)
|
||||
const classes = useMDStyles()
|
||||
|
||||
const unpairDialog = () => setOpen(true)
|
||||
|
||||
const [machineAction, { loading }] = useMutation(MACHINE_ACTION, {
|
||||
onError: ({ graphQLErrors, message }) => {
|
||||
const errorMessage = graphQLErrors[0] ? graphQLErrors[0].message : message
|
||||
setActionMessage(errorMessage)
|
||||
setErrorDialog(true)
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
<Dialog open={errorDialog} aria-labelledby="form-dialog-title">
|
||||
<DialogTitle
|
||||
id="customized-dialog-title"
|
||||
onClose={() => setErrorDialog(false)}>
|
||||
<H4>Error</H4>
|
||||
</DialogTitle>
|
||||
<DialogContent>{actionMessage}</DialogContent>
|
||||
</Dialog>
|
||||
<div className={classes.wrapper}>
|
||||
<div className={classnames(classes.row)}>
|
||||
<div className={classnames(classes.col)}>
|
||||
<div className={classnames(classes.row)}>
|
||||
<div className={classnames(classes.col, classes.col2)}>
|
||||
<div className={classes.innerRow}>
|
||||
<div>
|
||||
<Label>Statuses</Label>
|
||||
<div>
|
||||
{machine.statuses.map((status, index) => (
|
||||
<Status status={status} key={index} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label>Lamassu Support article</Label>
|
||||
<div>
|
||||
{machine.statuses.map((...[, index]) => (
|
||||
<span key={index} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={classnames(
|
||||
classes.col,
|
||||
classes.col2,
|
||||
classes.colDivider
|
||||
)}
|
||||
/>
|
||||
<div className={classnames(classes.col)}>
|
||||
<div className={classnames(classes.row)}>
|
||||
<div className={classnames(classes.col, classes.col2)}>
|
||||
<div className={classes.innerRow}>
|
||||
<div>
|
||||
<Label>Machine Model</Label>
|
||||
<div>{machine.machineModel}</div>
|
||||
</div>
|
||||
<div className={classes.commissionWrapper}>
|
||||
<Label>Address</Label>
|
||||
<div>{machine.machineLocation}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={classnames(classes.row)}>
|
||||
<div className={classnames(classes.col, classes.col2)}>
|
||||
<div className={classes.innerRow}>
|
||||
<div>
|
||||
<Label>Paired at</Label>
|
||||
<div>
|
||||
{moment(machine.pairedAt).format('YYYY-MM-DD HH:mm:ss')}
|
||||
</div>
|
||||
</div>
|
||||
<div className={classes.commissionWrapper}>
|
||||
<Label>Software update</Label>
|
||||
<div className={classes.innerRow}>
|
||||
{machine.softwareVersion && (
|
||||
<span className={classes.inlineChip}>
|
||||
{machine.softwareVersion}
|
||||
</span>
|
||||
)}
|
||||
<ActionButton
|
||||
className={classes.inlineChip}
|
||||
disabled
|
||||
color="primary"
|
||||
Icon={DownloadIcon}
|
||||
InverseIcon={DownloadReversedIcon}>
|
||||
Update
|
||||
</ActionButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={classnames(classes.row)}>
|
||||
<div className={classnames(classes.col, classes.col2)}>
|
||||
<div className={classes.innerRow}>
|
||||
<div>
|
||||
<Label>Printer</Label>
|
||||
<div>{machine.printer || 'unknown'}</div>
|
||||
</div>
|
||||
<div className={classes.commissionWrapper}>
|
||||
<Label>Actions</Label>
|
||||
<div className={classes.innerRow}>
|
||||
<ActionButton
|
||||
className={classes.inlineChip}
|
||||
color="primary"
|
||||
Icon={UnpairIcon}
|
||||
InverseIcon={UnpairReversedIcon}
|
||||
disabled={loading}
|
||||
onClick={unpairDialog}>
|
||||
Unpair
|
||||
</ActionButton>
|
||||
<ConfirmDialog
|
||||
open={dialogOpen}
|
||||
title="Unpair this machine?"
|
||||
subtitle={false}
|
||||
toBeConfirmed={machine.name}
|
||||
onConfirmed={() => {
|
||||
setOpen(false)
|
||||
machineAction({
|
||||
variables: {
|
||||
deviceId: machine.deviceId,
|
||||
action: 'unpair'
|
||||
}
|
||||
})
|
||||
}}
|
||||
onDissmised={() => {
|
||||
setOpen(false)
|
||||
}}
|
||||
/>
|
||||
<ActionButton
|
||||
className={classes.inlineChip}
|
||||
color="primary"
|
||||
Icon={RebootIcon}
|
||||
InverseIcon={RebootReversedIcon}
|
||||
disabled={loading}
|
||||
onClick={() => {
|
||||
machineAction({
|
||||
variables: {
|
||||
deviceId: machine.deviceId,
|
||||
action: 'reboot'
|
||||
}
|
||||
})
|
||||
}}>
|
||||
Reboot
|
||||
</ActionButton>
|
||||
<ActionButton
|
||||
className={classes.inlineChip}
|
||||
disabled={loading}
|
||||
color="primary"
|
||||
Icon={ShutdownIcon}
|
||||
InverseIcon={ShutdownReversedIcon}
|
||||
onClick={() => {
|
||||
machineAction({
|
||||
variables: {
|
||||
deviceId: machine.deviceId,
|
||||
action: 'shutdown'
|
||||
}
|
||||
})
|
||||
}}>
|
||||
Shutdown
|
||||
</ActionButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default MachineDetailsRow
|
||||
103
new-lamassu-admin/src/pages/maintenance/MachineStatus.js
Normal file
103
new-lamassu-admin/src/pages/maintenance/MachineStatus.js
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
import { makeStyles } from '@material-ui/core'
|
||||
import { useQuery } from '@apollo/react-hooks'
|
||||
import { gql } from 'apollo-boost'
|
||||
import moment from 'moment'
|
||||
import * as R from 'ramda'
|
||||
import React from 'react'
|
||||
|
||||
import ExpTable from '../../components/expandable-table/ExpTable'
|
||||
import { MainStatus } from '../../components/Status'
|
||||
import Title from '../../components/Title'
|
||||
import { ReactComponent as WarningIcon } from '../../styling/icons/status/pumpkin.svg'
|
||||
import { ReactComponent as ErrorIcon } from '../../styling/icons/status/tomato.svg'
|
||||
import { mainStyles } from '../Transactions/Transactions.styles'
|
||||
|
||||
import MachineDetailsRow from './MachineDetailsCard'
|
||||
|
||||
const GET_MACHINES = gql`
|
||||
{
|
||||
machines {
|
||||
name
|
||||
deviceId
|
||||
paired
|
||||
cashbox
|
||||
cassette1
|
||||
cassette2
|
||||
statuses {
|
||||
label
|
||||
type
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const useStyles = makeStyles(mainStyles)
|
||||
|
||||
const MachineStatus = () => {
|
||||
const classes = useStyles()
|
||||
|
||||
const { data: machinesResponse } = useQuery(GET_MACHINES)
|
||||
|
||||
const elements = [
|
||||
{
|
||||
header: 'Machine Name',
|
||||
size: 232,
|
||||
textAlign: 'left',
|
||||
view: m => m.name
|
||||
},
|
||||
{
|
||||
header: 'Status',
|
||||
size: 349,
|
||||
textAlign: 'left',
|
||||
view: m => <MainStatus statuses={m.statuses} />
|
||||
},
|
||||
{
|
||||
header: 'Last ping',
|
||||
size: 192,
|
||||
textAlign: 'left',
|
||||
view: m => moment(m.lastPing).fromNow()
|
||||
},
|
||||
{
|
||||
header: 'Ping Time',
|
||||
size: 155,
|
||||
textAlign: 'left',
|
||||
view: m => m.pingTime || 'unknown'
|
||||
},
|
||||
{
|
||||
header: 'Software Version',
|
||||
size: 201,
|
||||
textAlign: 'left',
|
||||
view: m => m.softwareVersion || 'unknown'
|
||||
},
|
||||
{
|
||||
size: 71
|
||||
}
|
||||
]
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={classes.titleWrapper}>
|
||||
<div className={classes.titleAndButtonsContainer}>
|
||||
<Title>Machine Status</Title>
|
||||
</div>
|
||||
<div className={classes.headerLabels}>
|
||||
<div>
|
||||
<WarningIcon />
|
||||
<span>Warning</span>
|
||||
</div>
|
||||
<div>
|
||||
<ErrorIcon />
|
||||
<span>Error</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ExpTable
|
||||
elements={elements}
|
||||
data={R.path(['machines'])(machinesResponse)}
|
||||
Details={MachineDetailsRow}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default MachineStatus
|
||||
|
|
@ -12,6 +12,8 @@ import Services from 'src/pages/Services/Services'
|
|||
import AuthRegister from 'src/pages/AuthRegister'
|
||||
import OperatorInfo from 'src/pages/OperatorInfo/OperatorInfo'
|
||||
|
||||
import MachineStatus from '../pages/maintenance/MachineStatus'
|
||||
|
||||
const tree = [
|
||||
{ key: 'transactions', label: 'Transactions', route: '/transactions' },
|
||||
// maintenence: { label: 'Maintenence', children: [{ label: 'Locale', route: '/locale' }] },
|
||||
|
|
@ -27,6 +29,11 @@ const tree = [
|
|||
key: 'server-logs',
|
||||
label: 'Server',
|
||||
route: '/maintenance/server-logs'
|
||||
},
|
||||
{
|
||||
key: 'machine-status',
|
||||
label: 'Machine Status',
|
||||
route: '/maintenance/machine-status'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
@ -80,6 +87,7 @@ const Routes = () => (
|
|||
<Route path="/maintenance/server-logs" component={ServerLogs} />
|
||||
<Route path="/transactions" component={Transactions} />
|
||||
<Route path="/register" component={AuthRegister} />
|
||||
<Route path="/maintenance/machine-status" component={MachineStatus} />
|
||||
</Switch>
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import { ReactComponent as AuthorizeIcon } from 'src/styling/icons/button/author
|
|||
import { ActionButton, Button, Link } from 'src/components/buttons'
|
||||
import { Radio, TextInput, Switch } from 'src/components/inputs'
|
||||
|
||||
import ConfirmDialog from '../components/ConfirmDialog'
|
||||
import {
|
||||
H1,
|
||||
H2,
|
||||
|
|
@ -119,7 +120,11 @@ story.add('Switch', () => (
|
|||
|
||||
story.add('Text Input', () => (
|
||||
<Wrapper>
|
||||
<TextInput color={select('Color', colors, 'amazonite')} />
|
||||
<TextInput
|
||||
name="text-input"
|
||||
touched={[]}
|
||||
color={select('Color', colors, 'amazonite')}
|
||||
/>
|
||||
</Wrapper>
|
||||
))
|
||||
|
||||
|
|
@ -141,6 +146,21 @@ story.add('Checkbox', () => (
|
|||
</Wrapper>
|
||||
))
|
||||
|
||||
story.add('ConfirmDialog', () => (
|
||||
<Wrapper>
|
||||
<ConfirmDialog
|
||||
open={boolean('open', true)}
|
||||
onDissmised={() => {
|
||||
window.alert('dissmised')
|
||||
}}
|
||||
onConfirmed={() => {
|
||||
window.alert('confirmed')
|
||||
}}
|
||||
toBeConfirmed="there-is-no-fate"
|
||||
/>
|
||||
</Wrapper>
|
||||
))
|
||||
|
||||
story.add('Radio', () => <Radio label="Hehe" />)
|
||||
|
||||
const typographyStory = storiesOf('Typography', module)
|
||||
|
|
|
|||
5
new-lamassu-admin/src/styling/icons/status/pumpkin.svg
Normal file
5
new-lamassu-admin/src/styling/icons/status/pumpkin.svg
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>icon/status/warning</title>
|
||||
<rect width="12" height="12" rx="3" ry="3" fill="#ff7311"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 289 B |
5
new-lamassu-admin/src/styling/icons/status/tomato.svg
Normal file
5
new-lamassu-admin/src/styling/icons/status/tomato.svg
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>icon/status/warning</title>
|
||||
<rect width="12" height="12" rx="3" ry="3" fill="#ff584a"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 289 B |
|
|
@ -119,10 +119,10 @@ export {
|
|||
spring2,
|
||||
spring3,
|
||||
tomato,
|
||||
pumpkin,
|
||||
mistyRose,
|
||||
java,
|
||||
neon,
|
||||
pumpkin,
|
||||
linen,
|
||||
// named colors
|
||||
primaryColor,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue