feat: add date range download dialog

This commit is contained in:
Luis Félix 2019-11-07 12:59:37 +00:00 committed by Josh Harvey
parent 74d592d892
commit 57c0b7cca1
14 changed files with 696 additions and 62 deletions

View file

@ -6508,8 +6508,7 @@
"ansi-regex": {
"version": "2.1.1",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"aproba": {
"version": "1.2.0",
@ -6530,14 +6529,12 @@
"balanced-match": {
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@ -6552,20 +6549,17 @@
"code-point-at": {
"version": "1.1.0",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"core-util-is": {
"version": "1.0.2",
@ -6682,8 +6676,7 @@
"inherits": {
"version": "2.0.3",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"ini": {
"version": "1.3.5",
@ -6695,7 +6688,6 @@
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@ -6710,7 +6702,6 @@
"version": "3.0.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@ -6718,14 +6709,12 @@
"minimist": {
"version": "0.0.8",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"minipass": {
"version": "2.3.5",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
@ -6744,7 +6733,6 @@
"version": "0.5.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"minimist": "0.0.8"
}
@ -6825,8 +6813,7 @@
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"object-assign": {
"version": "4.1.1",
@ -6838,7 +6825,6 @@
"version": "1.4.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"wrappy": "1"
}
@ -6924,8 +6910,7 @@
"safe-buffer": {
"version": "5.1.2",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"safer-buffer": {
"version": "2.1.2",
@ -6961,7 +6946,6 @@
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@ -6981,7 +6965,6 @@
"version": "3.0.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@ -7025,14 +7008,12 @@
"wrappy": {
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"yallist": {
"version": "3.0.3",
"bundled": true,
"dev": true,
"optional": true
"dev": true
}
}
},
@ -8064,7 +8045,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz",
"integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==",
"dev": true,
"requires": {
"es5-ext": "^0.10.50",
"type": "^1.0.1"
@ -8880,7 +8860,6 @@
"version": "0.10.51",
"resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.51.tgz",
"integrity": "sha512-oRpWzM2WcLHVKpnrcyB7OW8j/s67Ba04JCm0WnNv3RiABSvs7mrQlutB8DBv793gKcp0XENR8Il8WxGTlZ73gQ==",
"dev": true,
"requires": {
"es6-iterator": "~2.0.3",
"es6-symbol": "~3.1.1",
@ -8897,7 +8876,6 @@
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz",
"integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=",
"dev": true,
"requires": {
"d": "1",
"es5-ext": "^0.10.35",
@ -8914,7 +8892,6 @@
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.2.tgz",
"integrity": "sha512-/ZypxQsArlv+KHpGvng52/Iz8by3EQPxhmbuz8yFG89N/caTFBSbcXONDw0aMjy827gQg26XAjP4uXFvnfINmQ==",
"dev": true,
"requires": {
"d": "^1.0.1",
"es5-ext": "^0.10.51"
@ -12702,14 +12679,12 @@
"balanced-match": {
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@ -12729,8 +12704,7 @@
"concat-map": {
"version": "0.0.1",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"console-control-strings": {
"version": "1.1.0",
@ -14733,6 +14707,14 @@
"resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz",
"integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg=="
},
"moment-range": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/moment-range/-/moment-range-4.0.2.tgz",
"integrity": "sha512-n8sceWwSTjmz++nFHzeNEUsYtDqjgXgcOBzsHi+BoXQU2FW+eU92LUaK8gqOiSu5PG57Q9sYj1Fz4LRDj4FtKA==",
"requires": {
"es6-symbol": "^3.1.0"
}
},
"move-concurrently": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
@ -14842,8 +14824,7 @@
"next-tick": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz",
"integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=",
"dev": true
"integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw="
},
"nice-try": {
"version": "1.0.5",
@ -21979,8 +21960,7 @@
"type": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz",
"integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==",
"dev": true
"integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg=="
},
"type-check": {
"version": "0.3.2",

View file

@ -15,6 +15,7 @@
"jss-plugin-extend": "^10.0.0",
"lodash": "4.17.15",
"moment": "2.24.0",
"moment-range": "^4.0.2",
"qrcode.react": "0.9.3",
"react": "^16.10.2",
"react-dom": "^16.10.2",

View file

@ -0,0 +1,44 @@
import React from 'react'
import { makeStyles, Popover as MaterialPopover } from '@material-ui/core'
const styles = {
arrow: {
width: 0,
height: 0,
position: 'absolute',
borderStyle: 'solid',
margin: 5,
borderWidth: [[0, 15, 18, 15]],
borderLeftColor: 'transparent',
borderRightColor: 'transparent',
borderTopColor: 'transparent',
top: -18,
left: 138,
marginTop: 0,
marginBottom: 0,
borderColor: '#ffffff'
},
paper: {
overflow: 'visible'
}
}
const useStyles = makeStyles(styles)
const Popover = ({ children, ...props }) => {
const classes = useStyles()
return (
<MaterialPopover
classes={{
paper: classes.paper
}}
{...props}
>
{children}
<div class={classes.arrow} />
</MaterialPopover>
)
}
export default Popover

View file

@ -0,0 +1,144 @@
import React, { useState } from 'react'
import moment from 'moment'
import { toInteger } from 'lodash/fp'
import { makeStyles } from '@material-ui/core/styles'
import Tile from './Tile'
import { ReactComponent as Arrow } from '../../styling/icons/arrow/month_change.svg'
import { primaryColor, zircon, fontSecondary } from '../../styling/variables'
import typographyStyles from '../typography/styles'
const { label2 } = typographyStyles
const styles = {
navbar: {
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
padding: [[15, 15]],
color: primaryColor,
'& button': {
display: 'flex',
alignItems: 'center',
padding: 0,
border: 'none',
backgroundColor: zircon,
cursor: 'pointer',
borderRadius: '50%',
width: 20,
height: 20,
position: 'relative',
overflow: 'hidden',
'& svg': {
position: 'absolute',
left: 0
}
},
fontFamily: fontSecondary,
fontSize: 16,
fontWeight: 500
},
table: {
borderCollapse: 'collapse',
color: primaryColor,
'& tr': {
'&:first-child': {
paddingLeft: 5
},
'&:last-child': {
paddingRight: 5
}
},
'& th, & td': {
margin: 0,
padding: [[3, 0, 3, 0]]
},
'& th': {
extend: label2
}
}
}
const useStyles = makeStyles(styles)
const Calendar = ({ minDate, maxDate, handleSelect, ...props }) => {
const [currentDisplayedMonth, setCurrentDisplayedMonth] = useState(moment())
const classes = useStyles()
const weekdays = moment.weekdaysMin().map(day => day.slice(0, 1))
const firstDayOfMonth = (month) => toInteger(moment(month).startOf('month').format('d'))
const monthLength = (month) => toInteger(moment(month).endOf('month').format('D'))
const monthdays = (month) => {
const days = []
const lastMonth = moment(month).subtract(1, 'month')
for (let i = firstDayOfMonth(month) - 1; i >= 0; i--) {
days.push(moment(lastMonth).endOf('month').subtract(i, 'days'))
}
for (let j = 0; j < monthLength(month); j++) {
days.push(moment(month).startOf('month').add(j, 'days'))
}
const nextMonth = moment(month).add(1, 'month')
for (let k = 0; days.length < 42; k++) {
days.push(moment(nextMonth).startOf('month').add(k, 'days'))
}
return days
}
const getRow = (month, row) => monthdays(month).slice(row * 7 - 7, row * 7)
const handleNavPrev = (currentMonth) => {
const prevMonth = moment(currentMonth).subtract(1, 'month')
if (!minDate) setCurrentDisplayedMonth(prevMonth)
else setCurrentDisplayedMonth(prevMonth.isSameOrAfter(minDate, 'month') ? prevMonth : currentDisplayedMonth)
}
const handleNavNext = (currentMonth) => {
const nextMonth = moment(currentMonth).add(1, 'month')
if (!maxDate) setCurrentDisplayedMonth(nextMonth)
else setCurrentDisplayedMonth(nextMonth.isSameOrBefore(maxDate, 'month') ? nextMonth : currentDisplayedMonth)
}
return (
<div className={classes.wrapper}>
<div className={classes.navbar}>
<button onClick={() => handleNavPrev(currentDisplayedMonth)}>
<Arrow />
</button>
<span>{`${currentDisplayedMonth.format('MMMM')} ${currentDisplayedMonth.format('YYYY')}`}</span>
<button onClick={() => handleNavNext(currentDisplayedMonth)}>
<Arrow transform='rotate(180)' />
</button>
</div>
<table className={classes.table}>
<thead>
<tr>
{weekdays.map((day, key) => (
<th key={key}>{day}</th>
))}
</tr>
</thead>
<tbody>
{[1, 2, 3, 4, 5, 6, 7].map((row, key) => (
<tr key={key}>
{getRow(currentDisplayedMonth, row).map((day, key) => (
<td key={key} onClick={() => handleSelect(day, minDate, maxDate)}>
<Tile
isDisabled={(maxDate && day.isAfter(maxDate, 'day')) || (minDate && day.isBefore(minDate, 'day'))}
isLowerBound={day.isSame(props.from, 'day')}
isUpperBound={day.isSame(props.to, 'day')}
isBetween={day.isBetween(props.from, props.to, 'day', [])}
>
{day.format('D')}
</Tile>
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
)
}
export default Calendar

View file

@ -0,0 +1,131 @@
import React, { useState, useEffect } from 'react'
import classnames from 'classnames'
import { makeStyles } from '@material-ui/core/styles'
import Calendar from './Calendar'
import { ReactComponent as Arrow } from '../../styling/icons/arrow/download_logs.svg'
import { primaryColor, offColor, zircon } from '../../styling/variables'
import typographyStyles from '../typography/styles'
const { info1, label, label3 } = typographyStyles
const dateContainerStyles = {
wrapper: {
minWidth: 118
},
container: {
display: 'flex'
},
monthWeekDayContainer: {
display: 'flex',
flexDirection: 'column'
},
label: {
extend: label,
color: primaryColor
},
bigNumber: {
extend: info1,
marginRight: 7
},
monthYear: {
extend: label3,
color: primaryColor
},
weekDay: {
extend: label,
lineHeight: 1,
color: offColor
}
}
const dateContainerUseStyles = makeStyles(dateContainerStyles)
const DateContainer = ({ date, children, ...props }) => {
const classes = dateContainerUseStyles()
return (
<div className={classes.wrapper}>
<div className={classes.label}>{children}</div>
{date &&
<>
<div className={classes.container}>
<div className={classes.bigNumber}>{date.format('D')}</div>
<div className={classes.monthWeekDayContainer}>
<span className={classes.monthYear}>{`${date.format('MMM')} ${date.format('YYYY')}`}</span>
<span className={classes.weekDay}>{date.format('dddd')}</span>
</div>
</div>
</>}
</div>
)
}
const styles = {
wrapper: {
backgroundColor: 'white',
borderRadius: 10
},
dateThingyContainer: {
height: 80,
display: 'flex',
justifyContent: 'space-between',
backgroundColor: zircon,
padding: [[5, 15, 0, 15]]
},
arrowContainer: {
width: 39,
display: 'flex',
alignSelf: 'center',
alignItems: 'center'
},
arrow: {
margin: 'auto'
}
}
const useStyles = makeStyles(styles)
const DateRangePicker = ({ minDate, maxDate, className, handleChange, ...props }) => {
const [from, setFrom] = useState(null)
const [to, setTo] = useState(null)
useEffect(() => {
handleChange(from, to)
}, [to])
const classes = useStyles()
const handleSelect = (day, minDate, maxDate) => {
if ((maxDate && day.isAfter(maxDate, 'day')) || (minDate && day.isBefore(minDate, 'day'))) return
if (from && !to) {
if (day.isBefore(from, 'day')) {
setTo(from)
setFrom(day)
} else {
setTo(day)
}
} else {
setFrom(day)
setTo(null)
}
}
return (
<>
<div className={classnames(classes.wrapper, className)}>
<div className={classes.dateThingyContainer}>
<DateContainer date={from}>From</DateContainer>
<div className={classes.arrowContainer}>
<Arrow className={classes.arrow} />
</div>
<DateContainer date={to}>To</DateContainer>
</div>
<Calendar from={from} to={to} minDate={minDate} maxDate={maxDate} handleSelect={handleSelect} />
</div>
</>
)
}
export default DateRangePicker

View file

@ -0,0 +1,94 @@
import React from 'react'
import classnames from 'classnames'
import { makeStyles } from '@material-ui/core/styles'
import { primaryColor, spring2, spring3, fontSecondary, disabledColor } from '../../styling/variables'
const styles = {
wrapper: {
width: 45,
height: 26,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
position: 'relative'
},
button: {
border: 'none',
width: '100%',
height: '100%',
cursor: 'pointer',
padding: 0,
backgroundColor: 'transparent',
color: primaryColor,
zIndex: 2,
fontFamily: fontSecondary,
fontSize: 14,
fontWeight: 500
},
lowerBound: {
width: [['50%', '!important']],
left: '50%'
},
upperBound: {
width: [['50%', '!important']],
right: '50%'
},
selected: {
width: 26,
height: 26,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: spring2,
borderRadius: '50%',
position: 'absolute',
zIndex: 1
},
between: {
position: 'absolute',
width: '100%',
height: '100%',
zIndex: 0,
backgroundColor: spring3
},
disabled: {
color: disabledColor,
cursor: 'default'
}
}
const useStyles = makeStyles(styles)
const Tile = ({ isLowerBound, isUpperBound, isBetween, isDisabled, children, ...props }) => {
const classes = useStyles()
const selected = isLowerBound || isUpperBound
const rangeClasses = {
[classes.between]: isBetween && !(isLowerBound && isUpperBound),
[classes.lowerBound]: isLowerBound && !isUpperBound,
[classes.upperBound]: isUpperBound && !isLowerBound
}
const buttonWrapperClasses = {
[classes.wrapper]: true,
[classes.selected]: selected
}
const buttonClasses = {
[classes.button]: true,
[classes.disabled]: isDisabled
}
return (
<div className={classes.wrapper}>
<div className={classnames(rangeClasses)} />
<div className={classnames(buttonWrapperClasses)}>
<button className={classnames(buttonClasses)}>
{children}
</button>
</div>
</div>
)
}
export default Tile

View file

@ -0,0 +1,32 @@
import React from 'react'
import classnames from 'classnames'
import { withStyles } from '@material-ui/styles'
import FormControlLabel from '@material-ui/core/FormControlLabel'
import { Radio as MaterialRadio, RadioGroup as MaterialRadioGroup } from '@material-ui/core'
import { secondaryColor } from '../../../styling/variables'
const GreenRadio = withStyles({
root: {
color: secondaryColor,
'&$checked': {
color: secondaryColor
}
},
checked: {}
})(props => <MaterialRadio color='default' {...props} />)
const RadioGroup = ({ name, value, labels, ariaLabel, onChange, className, ...props }) => {
return (
<>
{labels && (
<MaterialRadioGroup aria-label={ariaLabel} name={name} value={value} onChange={onChange} className={classnames(className)}>
{labels.map((label, idx) => (
<FormControlLabel key={idx} value={idx} control={<GreenRadio />} label={label} />
))}
</MaterialRadioGroup>
)}
</>
)
}
export default RadioGroup

View file

@ -5,5 +5,6 @@ import Radio from './base/Radio'
import TextInput from './base/TextInput'
import Switch from './base/Switch'
import Select from './base/Select'
import RadioGroup from './base/RadioGroup'
export { Autocomplete, AutocompleteMultiple, TextInput, Radio, Checkbox, Switch, Select }
export { Autocomplete, AutocompleteMultiple, TextInput, Radio, Checkbox, Switch, Select, RadioGroup }

View file

@ -119,6 +119,11 @@ export default {
fontWeight: 500,
color: fontColor
},
label3: {
fontSize: fontSize5,
fontFamily: fontSecondary,
fontWeight: 700
},
select: {
fontSize: fontSize3,
fontFamily: fontSecondary,

View file

@ -1,26 +1,28 @@
import React, { useState } from 'react'
import FileSaver from 'file-saver'
import { concat, uniq } from 'lodash/fp'
import classnames from 'classnames'
import { concat, uniq, toInteger } from 'lodash/fp'
import moment from 'moment'
import useAxios from '@use-hooks/axios'
import { makeStyles } from '@material-ui/core'
import Title from '../components/Title'
import { Info3 } from '../components/typography'
import { FeatureButton, SimpleButton } from '../components/buttons'
import { FeatureButton, SimpleButton, Link } from '../components/buttons'
import { Table, TableHead, TableRow, TableHeader, TableBody, TableCell } from '../components/table'
import { Select } from '../components/inputs'
import { Select, RadioGroup } from '../components/inputs'
import Uptime from '../components/Uptime'
import DateRangePicker from '../components/date-range-picker/DateRangePicker'
import Popover from '../components/Popover'
import { ReactComponent as Download } from '../styling/icons/button/download/zodiac.svg'
import { ReactComponent as DownloadActive } from '../styling/icons/button/download/white.svg'
import { makeStyles } from '@material-ui/core'
import typographyStyles from '../components/typography/styles'
import { comet } from '../styling/variables'
import { primaryColor, comet } from '../styling/variables'
import styles from './Logs.styles'
import logPageHeaderStyles from './LogPageHeader.styles'
const { regularLabel } = typographyStyles
import typographyStyles from '../components/typography/styles'
const { regularLabel, h4 } = typographyStyles
const { tableWrapper } = styles
const { titleAndButtonsContainer, buttonsWrapper } = logPageHeaderStyles
@ -55,6 +57,40 @@ styles.uptimeContainer = {
styles.titleAndButtonsContainer = titleAndButtonsContainer
styles.buttonsWrapper = buttonsWrapper
styles.popoverContent = {
minWidth: 315
}
styles.popoverHeader = {
extend: h4,
padding: [[20, 15, 0, 15]]
}
styles.radioButtonsContainer = {
padding: [[10, 15, 10, 15]]
}
styles.radioButtons = {
display: 'flex',
justifyContent: 'space-between',
flexDirection: 'row',
color: primaryColor
}
styles.dateRangePickerShowing = {
display: 'block',
height: '100%'
}
styles.dateRangePickerHidden = {
display: 'none',
height: 0
}
styles.download = {
padding: [[30, 15, 30, 15]]
}
const useStyles = makeStyles(styles)
const SHOW_ALL = 'Show all'
@ -68,6 +104,9 @@ const Logs = () => {
const [logLevel, setLogLevel] = useState(SHOW_ALL)
const [version, setVersion] = useState(null)
const [processStates, setProcessStates] = useState(null)
const [radioButtons, setRadioButtons] = useState(0)
const [range, setRange] = useState(null)
const [anchorEl, setAnchorEl] = useState(null)
const classes = useStyles()
@ -116,12 +155,34 @@ const Logs = () => {
}
})
const handleLogLevelChange = (item) => setLogLevel(item)
const formatDateFile = date => {
return moment(date).format('YYYY-MM-DD_HH-mm')
}
const dateRangePickerClasses = {
[classes.dateRangePickerShowing]: radioButtons === 1,
[classes.dateRangePickerHidden]: radioButtons === 0
}
const handleOpenRangePicker = (event) => {
setAnchorEl(event.currentTarget)
}
const handleCloseRangePicker = () => {
setAnchorEl(null)
}
const handleRadioButtons = (event) => {
setRadioButtons(toInteger(event.target.value))
}
const handleRangeChange = (from, to) => {
setRange({ from, to })
}
const open = Boolean(anchorEl)
const id = open ? 'date-range-popover' : undefined
return (
<>
<div className={classes.titleWrapper}>
@ -132,14 +193,67 @@ const Logs = () => {
<FeatureButton
Icon={Download}
InverseIcon={DownloadActive}
onClick={() => {
const text = logsResponse.data.logs.map(it => JSON.stringify(it)).join('\n')
const blob = new window.Blob([text], {
type: 'text/plain;charset=utf-8'
})
FileSaver.saveAs(blob, `${formatDateFile(new Date())}_server`)
}}
aria-describedby={id}
variant='contained'
onClick={handleOpenRangePicker}
/>
<Popover
id={id}
open={open}
anchorEl={anchorEl}
onClose={handleCloseRangePicker}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'center'
}}
transformOrigin={{
vertical: 'top',
horizontal: 'center'
}}
>
<div className={classes.popoverContent}>
<div className={classes.popoverHeader}>
Download logs
</div>
<div className={classes.radioButtonsContainer}>
<RadioGroup
name='logs-select'
value={radioButtons}
labels={['All logs', 'Date range']}
ariaLabel='logs-select'
onChange={handleRadioButtons}
className={classes.radioButtons}
/>
</div>
<DateRangePicker
maxDate={moment()}
handleChange={handleRangeChange}
className={classnames(dateRangePickerClasses)}
/>
<div className={classes.download}>
<Link
color='primary'
onClick={() => {
if (radioButtons === 0) {
const text = logsResponse.data.logs.map(it => JSON.stringify(it)).join('\n')
const blob = new window.Blob([text], {
type: 'text/plain;charset=utf-8'
})
FileSaver.saveAs(blob, `${formatDateFile(new Date())}_server`)
} else if (radioButtons === 1 && range.from && range.to) {
const text = logsResponse.data.logs.filter((log) => moment(log.timestamp).isBetween(range.from, range.to, 'day', '[]')).map(it => JSON.stringify(it)).join('\n')
const blob = new window.Blob([text], {
type: 'text/plain;charset=utf-8'
})
FileSaver.saveAs(blob, `${formatDateFile(range.from)}_${formatDateFile(range.to)}_server`)
}
}}
>
Download
</Link>
</div>
</div>
</Popover>
<SimpleButton className={classes.button} disabled={loading} onClick={sendSnapshot}>
Share with Lamassu
</SimpleButton>
@ -156,7 +270,7 @@ const Logs = () => {
<div className={classes.headerLine2}>
{logsResponse && (
<Select
onSelectedItemChange={handleLogLevelChange}
onSelectedItemChange={setLogLevel}
label='Level'
items={concat([SHOW_ALL], uniq(logsResponse.data.logs.map(log => log.logLevel)))}
default={SHOW_ALL}

View file

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="23px" height="17px" viewBox="0 0 23 17" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 59.1 (86144) - https://sketch.com -->
<title>arrow download logs</title>
<desc>Created with Sketch.</desc>
<g id="Symbols" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
<g id="pop-up/action/download-logs/date-range-copy-3" transform="translate(-117.000000, -116.000000)" stroke="#1B2559" stroke-width="2">
<g id="arrow-download-logs" transform="translate(128.500000, 124.500000) rotate(-90.000000) translate(-128.500000, -124.500000) translate(121.000000, 114.000000)">
<polyline id="Path-3" points="0 13.3571429 7.14285714 20.5 14.2857143 13.3571429"></polyline>
<line x1="7.14285714" y1="0.142857143" x2="7.14285714" y2="20.1428571" id="Path-4"></line>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 59.1 (86144) - https://sketch.com -->
<title>icon/sf-contain-b copy 5</title>
<desc>Created with Sketch.</desc>
<g id="Symbols" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="pop-up/action/download-logs/date-range-copy-2" transform="translate(-20.000000, -187.000000)">
<g id="icon/sf-contain-b-copy-5" transform="translate(30.000000, 197.000000) rotate(-270.000000) translate(-30.000000, -197.000000) translate(20.000000, 187.000000)">
<g id="icon/sf-small/wizzard" stroke-linecap="round" stroke-linejoin="round">
<g transform="translate(6.666667, 6.000000)" id="Group">
<g>
<polyline id="Path-3" stroke="#1B2559" stroke-width="2" points="0 4.83333333 3.33333333 8.16666667 6.66666667 4.83333333"></polyline>
<line x1="3.33333333" y1="0.25" x2="3.33333333" y2="6.5" id="Path-4" stroke="#1B2559" stroke-width="2"></line>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 59.1 (86144) - https://sketch.com -->
<title>icon/sf-contain-b copy 5</title>
<desc>Created with Sketch.</desc>
<defs>
<circle id="path-1" cx="10" cy="10" r="10"></circle>
</defs>
<g id="Symbols" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="pop-up/action/download-logs/date-range-copy-2" transform="translate(-20.000000, -187.000000)">
<g id="icon/sf-contain-b-copy-5" transform="translate(30.000000, 197.000000) rotate(-270.000000) translate(-30.000000, -197.000000) translate(20.000000, 187.000000)">
<mask id="mask-2" fill="white">
<use xlink:href="#path-1"></use>
</mask>
<use id="Mask" fill="#EBEFFF" fill-rule="nonzero" xlink:href="#path-1"></use>
<g id="icon/sf-small/wizzard" mask="url(#mask-2)" stroke-linecap="round" stroke-linejoin="round">
<g transform="translate(6.666667, 6.000000)" id="Group">
<g>
<polyline id="Path-3" stroke="#1B2559" stroke-width="2" points="0 4.83333333 3.33333333 8.16666667 6.66666667 4.83333333"></polyline>
<line x1="3.33333333" y1="0.25" x2="3.33333333" y2="6.5" id="Path-4" stroke="#1B2559" stroke-width="2"></line>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 59.1 (86144) - https://sketch.com -->
<title>icon/sf-contain-b copy 4</title>
<desc>Created with Sketch.</desc>
<defs>
<circle id="path-1" cx="10" cy="10" r="10"></circle>
</defs>
<g id="Symbols" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="pop-up/action/download-logs/date-range-copy-2" transform="translate(-232.000000, -187.000000)">
<g id="icon/sf-contain-b-copy-4" transform="translate(242.000000, 197.000000) scale(-1, 1) rotate(-270.000000) translate(-242.000000, -197.000000) translate(232.000000, 187.000000)">
<mask id="mask-2" fill="white">
<use xlink:href="#path-1"></use>
</mask>
<use id="Mask" fill="#EBEFFF" fill-rule="nonzero" xlink:href="#path-1"></use>
<g id="icon/sf-small/wizzard" mask="url(#mask-2)" stroke-linecap="round" stroke-linejoin="round">
<g transform="translate(6.666667, 6.000000)" id="Group">
<g>
<polyline id="Path-3" stroke="#1B2559" stroke-width="2" points="0 4.83333333 3.33333333 8.16666667 6.66666667 4.83333333"></polyline>
<line x1="3.33333333" y1="0.25" x2="3.33333333" y2="6.5" id="Path-4" stroke="#1B2559" stroke-width="2"></line>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB