feat: add new server log page
This commit is contained in:
parent
fc1951c4b2
commit
703c5d7c91
38 changed files with 2844 additions and 29 deletions
14
lib/db.js
14
lib/db.js
|
|
@ -1,6 +1,9 @@
|
|||
const Pgp = require('pg-promise')
|
||||
const uuid = require('uuid')
|
||||
const _ = require('lodash/fp')
|
||||
const psqlUrl = require('../lib/options').postgresql
|
||||
const logger = require('./logger')
|
||||
const eventBus = require('./event-bus')
|
||||
|
||||
const pgp = Pgp({
|
||||
pgNative: true,
|
||||
|
|
@ -15,4 +18,15 @@ const pgp = Pgp({
|
|||
})
|
||||
|
||||
const db = pgp(psqlUrl)
|
||||
|
||||
eventBus.subscribe('log', args => {
|
||||
const { level, message, meta } = args
|
||||
|
||||
const sql = `insert into server_logs
|
||||
(id, device_id, message, log_level, meta) values ($1, $2, $3, $4, $5) returning *`
|
||||
|
||||
db.one(sql, [uuid.v4(), '', message, level, meta])
|
||||
.then(_.mapKeys(_.camelCase))
|
||||
})
|
||||
|
||||
module.exports = db
|
||||
|
|
|
|||
29
lib/event-bus.js
Normal file
29
lib/event-bus.js
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
// Adapted from https://medium.com/@soffritti.pierfrancesco/create-a-simple-event-bus-in-javascript-8aa0370b3969
|
||||
|
||||
const uuid = require('uuid')
|
||||
const _ = require('lodash/fp')
|
||||
|
||||
const subscriptions = {}
|
||||
|
||||
function subscribe (eventType, callback) {
|
||||
const id = uuid.v1()
|
||||
|
||||
if (!subscriptions[eventType]) subscriptions[eventType] = {}
|
||||
|
||||
subscriptions[eventType][id] = callback
|
||||
|
||||
return {
|
||||
unsubscribe: () => {
|
||||
delete subscriptions[eventType][id]
|
||||
if (_.keys(subscriptions[eventType]).length === 0) delete subscriptions[eventType]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function publish (eventType, arg) {
|
||||
if (!subscriptions[eventType]) return
|
||||
|
||||
_.keys(subscriptions[eventType]).forEach(key => subscriptions[eventType][key](arg))
|
||||
}
|
||||
|
||||
module.exports = { subscribe, publish }
|
||||
|
|
@ -1,11 +1,15 @@
|
|||
const winston = require('winston')
|
||||
const Postgres = require('./pg-transport')
|
||||
const options = require('./options')
|
||||
const _ = require('lodash/fp')
|
||||
|
||||
const logger = new winston.Logger({
|
||||
level: options.logLevel,
|
||||
transports: [
|
||||
new (winston.transports.Console)({ timestamp: true, colorize: true })
|
||||
new (winston.transports.Console)({ timestamp: true, colorize: true }),
|
||||
new Postgres({
|
||||
connectionString: options.postgresql,
|
||||
tableName: 'server_logs'
|
||||
})
|
||||
],
|
||||
rewriters: [
|
||||
(...[,, meta]) => meta instanceof Error ? { message: meta.message, stack: meta.stack } : meta
|
||||
|
|
|
|||
|
|
@ -7,7 +7,9 @@ const got = require('got')
|
|||
const supportLogs = require('../support_logs')
|
||||
const machineLoader = require('../machine-loader')
|
||||
const logs = require('../logs')
|
||||
const serverLogs = require('./server-logs')
|
||||
|
||||
const supervisor = require('./supervisor')
|
||||
const funding = require('./funding')
|
||||
const config = require('./config')
|
||||
|
||||
|
|
@ -56,6 +58,28 @@ app.post('/api/support_logs', (req, res, next) => {
|
|||
.catch(next)
|
||||
})
|
||||
|
||||
app.get('/api/version', (req, res, next) => {
|
||||
res.send(require('../../package.json').version)
|
||||
})
|
||||
|
||||
app.get('/api/uptimes', (req, res, next) => {
|
||||
return supervisor.getAllProcessInfo()
|
||||
.then(r => res.send(r))
|
||||
.catch(next)
|
||||
})
|
||||
|
||||
app.post('/api/server_support_logs', (req, res, next) => {
|
||||
return serverLogs.insert()
|
||||
.then(r => res.send(r))
|
||||
.catch(next)
|
||||
})
|
||||
|
||||
app.get('/api/server_logs', (req, res, next) => {
|
||||
return serverLogs.getServerLogs()
|
||||
.then(r => res.send(r))
|
||||
.catch(next)
|
||||
})
|
||||
|
||||
function dbNotify () {
|
||||
return got.post('http://localhost:3030/dbChange')
|
||||
.catch(e => console.error('Error: lamassu-server not responding'))
|
||||
|
|
|
|||
26
lib/new-admin/server-logs.js
Normal file
26
lib/new-admin/server-logs.js
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
const _ = require('lodash/fp')
|
||||
const uuid = require('uuid')
|
||||
|
||||
const db = require('../db')
|
||||
|
||||
const NUM_RESULTS = 500
|
||||
|
||||
function getServerLogs (until = new Date().toISOString()) {
|
||||
const sql = `select id, log_level, timestamp, message from server_logs
|
||||
order by timestamp desc
|
||||
limit $1`
|
||||
|
||||
return Promise.all([db.any(sql, [ NUM_RESULTS ])])
|
||||
.then(([logs]) => ({
|
||||
logs: _.map(_.mapKeys(_.camelCase), logs)
|
||||
}))
|
||||
}
|
||||
|
||||
function insert () {
|
||||
const sql = `insert into server_support_logs
|
||||
(id) values ($1) returning *`
|
||||
return db.one(sql, [uuid.v4()])
|
||||
.then(_.mapKeys(_.camelCase))
|
||||
}
|
||||
|
||||
module.exports = { getServerLogs, insert }
|
||||
58
lib/new-admin/supervisor.js
Normal file
58
lib/new-admin/supervisor.js
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
const xmlrpc = require('xmlrpc')
|
||||
const logger = require('../logger')
|
||||
const { promisify } = require('util')
|
||||
|
||||
function getAllProcessInfo () {
|
||||
const convertStates = (state) => {
|
||||
// From http://supervisord.org/subprocess.html#process-states
|
||||
switch (state) {
|
||||
case 'STOPPED':
|
||||
return 'STOPPED'
|
||||
case 'STARTING':
|
||||
return 'RUNNING'
|
||||
case 'RUNNING':
|
||||
return 'RUNNING'
|
||||
case 'BACKOFF':
|
||||
return 'FATAL'
|
||||
case 'STOPPING':
|
||||
return 'STOPPED'
|
||||
case 'EXITED':
|
||||
return 'STOPPED'
|
||||
case 'UNKNOWN':
|
||||
return 'FATAL'
|
||||
default:
|
||||
logger.error(`Supervisord returned an unsupported state: ${state}`)
|
||||
return 'FATAL'
|
||||
}
|
||||
}
|
||||
|
||||
const client = xmlrpc.createClient({
|
||||
host: 'localhost',
|
||||
port: '9001',
|
||||
path: '/RPC2'
|
||||
})
|
||||
|
||||
client.methodCall[promisify.custom] = (method, params) => {
|
||||
return new Promise((resolve, reject) => client.methodCall(method, params, (err, value) => {
|
||||
if (err) reject(err)
|
||||
else resolve(value)
|
||||
}))
|
||||
}
|
||||
|
||||
return promisify(client.methodCall)('supervisor.getAllProcessInfo', [])
|
||||
.then((value) => {
|
||||
return value.map(process => (
|
||||
{
|
||||
name: process.name,
|
||||
state: convertStates(process.statename),
|
||||
uptime: (process.statename === 'RUNNING') ? new Date(process.now) - new Date(process.start) : 0
|
||||
}
|
||||
))
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error.code === 'ECONNREFUSED') logger.error('Failed to connect to supervisord HTTP server.')
|
||||
else logger.error(error)
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = { getAllProcessInfo }
|
||||
39
lib/pg-transport.js
Normal file
39
lib/pg-transport.js
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
const Transport = require('winston-transport')
|
||||
const eventBus = require('./event-bus')
|
||||
|
||||
//
|
||||
// Inherit from `winston-transport` so you can take advantage
|
||||
// of the base functionality and `.exceptions.handle()`.
|
||||
//
|
||||
module.exports = class CustomTransport extends Transport {
|
||||
constructor (opts) {
|
||||
super(opts)
|
||||
|
||||
//
|
||||
// Consume any custom options here. e.g.:
|
||||
// - Connection information for databases
|
||||
// - Authentication information for APIs (e.g. loggly, papertrail,
|
||||
// logentries, etc.).
|
||||
//
|
||||
this.tableName = opts.tableName || 'winston_logs'
|
||||
|
||||
if (!opts.connectionString) {
|
||||
throw new Error('You have to define connectionString')
|
||||
}
|
||||
|
||||
this.connectionString = opts.connectionString
|
||||
}
|
||||
|
||||
log (level, message, meta, callback) {
|
||||
if (!callback) callback = () => {}
|
||||
|
||||
setImmediate(() => {
|
||||
this.emit('logged', level, message, meta)
|
||||
})
|
||||
|
||||
// Perform the writing to the remote service
|
||||
eventBus.publish('log', { level, message, meta })
|
||||
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,14 @@ const db = require('./db')
|
|||
|
||||
exports.up = function (next) {
|
||||
var sqls = [
|
||||
'create table server_logs ( ' +
|
||||
'id uuid PRIMARY KEY, ' +
|
||||
'device_id text, ' +
|
||||
'log_level text, ' +
|
||||
'timestamp timestamptz DEFAULT now(), ' +
|
||||
'message text, ' +
|
||||
'meta json)',
|
||||
|
||||
'CREATE TABLE IF NOT EXISTS user_config ( ' +
|
||||
'id serial PRIMARY KEY, ' +
|
||||
'type text NOT NULL, ' +
|
||||
|
|
|
|||
15
migrations/1572524820075-server-support-logs.js
Normal file
15
migrations/1572524820075-server-support-logs.js
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
var db = require('./db')
|
||||
|
||||
exports.up = function (next) {
|
||||
const sql =
|
||||
[`create table server_support_logs (
|
||||
id uuid PRIMARY KEY,
|
||||
timestamp timestamptz not null default now() )`
|
||||
]
|
||||
|
||||
db.multi(sql, next)
|
||||
}
|
||||
|
||||
exports.down = function (next) {
|
||||
next()
|
||||
}
|
||||
63
new-lamassu-admin/src/components/Uptime.js
Normal file
63
new-lamassu-admin/src/components/Uptime.js
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
import React from 'react'
|
||||
import { floor, lowerCase, startCase } from 'lodash/fp'
|
||||
import { makeStyles } from '@material-ui/core'
|
||||
import classnames from 'classnames'
|
||||
|
||||
import { spring3, spring2, mistyRose, tomato, zircon, comet, white, fontSecondary } from '../styling/variables'
|
||||
import typographyStyles from './typography/styles'
|
||||
const { label } = typographyStyles
|
||||
|
||||
const styles = {
|
||||
uptimeContainer: {
|
||||
display: 'inline-block',
|
||||
minWidth: 120,
|
||||
margin: '0 20px 0 20px'
|
||||
},
|
||||
name: {
|
||||
paddingLeft: 8,
|
||||
color: comet
|
||||
},
|
||||
uptime: {
|
||||
extend: label,
|
||||
textAlign: 'center',
|
||||
padding: 4
|
||||
},
|
||||
running: {
|
||||
backgroundColor: spring3,
|
||||
color: spring2
|
||||
},
|
||||
notRunning: {
|
||||
backgroundColor: mistyRose,
|
||||
color: tomato
|
||||
}
|
||||
}
|
||||
|
||||
const useStyles = makeStyles(styles)
|
||||
|
||||
const Uptime = ({ process, ...props }) => {
|
||||
const classes = useStyles()
|
||||
|
||||
const uptimeClassNames = {
|
||||
[classes.uptime]: true,
|
||||
[classes.running]: process.state === 'RUNNING',
|
||||
[classes.notRunning]: process.state !== 'RUNNING'
|
||||
}
|
||||
|
||||
const uptime = (time) => {
|
||||
if (time < 60) return `${time}s`
|
||||
if (time < 3600) return `${floor(time / 60, 0)}m`
|
||||
if (time < 86400) return `${floor(time / 60 / 60, 0)}h`
|
||||
return `${floor(time / 60 / 60 / 24, 0)}d`
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classes.uptimeContainer}>
|
||||
<div className={classes.name}>{lowerCase(process.name)}</div>
|
||||
<div className={classnames(uptimeClassNames)}>
|
||||
{process.state === 'RUNNING' ? `Running for ${uptime(process.uptime)}` : startCase(lowerCase(process.state))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Uptime
|
||||
|
|
@ -21,7 +21,7 @@ const ActionButton = memo(({ className, Icon, InverseIcon, color, children, ...p
|
|||
<div className={classnames(classes.actionButtonIcon, classes.actionButtonIconActive)}>
|
||||
<InverseIcon />
|
||||
</div>}
|
||||
<div>{children}</div>
|
||||
{children && <div>{children}</div>}
|
||||
</button>
|
||||
)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -0,0 +1,70 @@
|
|||
import {
|
||||
white,
|
||||
fontColor,
|
||||
subheaderColor,
|
||||
subheaderDarkColor,
|
||||
offColor,
|
||||
offDarkColor
|
||||
} from '../../styling/variables'
|
||||
|
||||
const colors = (color1, color2, color3) => {
|
||||
return {
|
||||
backgroundColor: color1,
|
||||
'&:hover': {
|
||||
backgroundColor: color2
|
||||
},
|
||||
'&:active': {
|
||||
backgroundColor: color3
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const buttonHeight = 45
|
||||
|
||||
export default {
|
||||
baseButton: {
|
||||
extend: colors(subheaderColor, subheaderDarkColor, offColor),
|
||||
cursor: 'pointer',
|
||||
border: 'none',
|
||||
outline: 0,
|
||||
height: buttonHeight,
|
||||
color: fontColor,
|
||||
'&:active': {
|
||||
color: white
|
||||
}
|
||||
},
|
||||
primary: {
|
||||
extend: colors(subheaderColor, subheaderDarkColor, offColor),
|
||||
'&:active': {
|
||||
color: white,
|
||||
'& $buttonIcon': {
|
||||
display: 'none'
|
||||
},
|
||||
'& $buttonIconActive': {
|
||||
display: 'block'
|
||||
}
|
||||
},
|
||||
'& $buttonIconActive': {
|
||||
display: 'none'
|
||||
}
|
||||
},
|
||||
secondary: {
|
||||
extend: colors(offColor, offDarkColor, white),
|
||||
color: white,
|
||||
'&:active': {
|
||||
color: fontColor,
|
||||
'& $buttonIcon': {
|
||||
display: 'flex'
|
||||
},
|
||||
'& $buttonIconActive': {
|
||||
display: 'none'
|
||||
}
|
||||
},
|
||||
'& $buttonIcon': {
|
||||
display: 'none'
|
||||
},
|
||||
'& $buttonIconActive': {
|
||||
display: 'flex'
|
||||
}
|
||||
}
|
||||
}
|
||||
50
new-lamassu-admin/src/components/buttons/FeatureButton.js
Normal file
50
new-lamassu-admin/src/components/buttons/FeatureButton.js
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
import React, { memo } from 'react'
|
||||
import classnames from 'classnames'
|
||||
import { makeStyles } from '@material-ui/core/styles'
|
||||
|
||||
import baseButtonStyles from './BaseButton.styles'
|
||||
|
||||
const { baseButton, primary } = baseButtonStyles
|
||||
|
||||
const svgSize = 25
|
||||
|
||||
const styles = {
|
||||
featureButton: {
|
||||
extend: baseButton,
|
||||
width: baseButton.height,
|
||||
borderRadius: baseButton.height / 2,
|
||||
display: 'flex'
|
||||
},
|
||||
primary,
|
||||
buttonIcon: {
|
||||
margin: 'auto',
|
||||
'& svg': {
|
||||
width: svgSize,
|
||||
height: svgSize
|
||||
}
|
||||
},
|
||||
buttonIconActive: {} // required to extend primary
|
||||
}
|
||||
|
||||
const useStyles = makeStyles(styles)
|
||||
|
||||
const FeatureButton = memo(({ className, Icon, InverseIcon, ...props }) => {
|
||||
const classes = useStyles()
|
||||
|
||||
const classNames = {
|
||||
[classes.featureButton]: true,
|
||||
[classes.primary]: true
|
||||
}
|
||||
|
||||
return (
|
||||
<button className={classnames(classNames, className)} {...props}>
|
||||
{Icon && <div className={classes.buttonIcon}><Icon /></div>}
|
||||
{InverseIcon &&
|
||||
<div className={classnames(classes.buttonIcon, classes.buttonIconActive)}>
|
||||
<InverseIcon />
|
||||
</div>}
|
||||
</button>
|
||||
)
|
||||
})
|
||||
|
||||
export default FeatureButton
|
||||
|
|
@ -1,9 +1,27 @@
|
|||
import React, { memo } from 'react'
|
||||
import classnames from 'classnames'
|
||||
|
||||
import baseButtonStyles from './BaseButton.styles'
|
||||
import { makeStyles } from '@material-ui/core/styles'
|
||||
|
||||
const { baseButton } = baseButtonStyles
|
||||
|
||||
const styles = {
|
||||
button: {
|
||||
extend: baseButton,
|
||||
borderRadius: baseButton.height / 2,
|
||||
outline: 0,
|
||||
padding: '0 20px'
|
||||
}
|
||||
}
|
||||
|
||||
const useStyles = makeStyles(styles)
|
||||
|
||||
const SimpleButton = memo(({ className, children, color, size, ...props }) => {
|
||||
const classes = useStyles()
|
||||
|
||||
return (
|
||||
<button className={classnames('simple-button', className)} {...props}>
|
||||
<button className={classnames(classes.button, className)} {...props}>
|
||||
{children}
|
||||
</button>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,5 +2,6 @@ import Button from './Button'
|
|||
import Link from './Link'
|
||||
import SimpleButton from './SimpleButton'
|
||||
import ActionButton from './ActionButton'
|
||||
import FeatureButton from './FeatureButton'
|
||||
|
||||
export { Button, Link, SimpleButton, ActionButton }
|
||||
export { Button, Link, SimpleButton, ActionButton, FeatureButton }
|
||||
|
|
|
|||
60
new-lamassu-admin/src/components/fake-table/EditableTable.js
Normal file
60
new-lamassu-admin/src/components/fake-table/EditableTable.js
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
import React, { useState } from 'react'
|
||||
|
||||
import {
|
||||
Td,
|
||||
Tr,
|
||||
THead,
|
||||
TBody,
|
||||
Table
|
||||
} from './Table'
|
||||
|
||||
import { Link } from '../../components/buttons'
|
||||
|
||||
const EditableRow = ({ elements, cancel, save }) => {
|
||||
const [editing, setEditing] = useState(false)
|
||||
const innerCancel = () => {
|
||||
setEditing(false)
|
||||
cancel()
|
||||
}
|
||||
|
||||
return (
|
||||
<Tr>
|
||||
{elements.map(({ size, edit, view }, idx) => (
|
||||
<Td key={idx} size={size}>{editing ? edit : view}</Td>
|
||||
))}
|
||||
<Td>
|
||||
{editing ? (
|
||||
<>
|
||||
<Link style={{ marginRight: '20px' }} color='secondary' onClick={innerCancel}>
|
||||
Cancel
|
||||
</Link>
|
||||
<Link color='primary' onClick={save}>
|
||||
Save
|
||||
</Link>
|
||||
</>
|
||||
) : (
|
||||
<Link color='primary' onClick={() => setEditing(true)}>
|
||||
Edit
|
||||
</Link>
|
||||
)}
|
||||
</Td>
|
||||
</Tr>
|
||||
)
|
||||
}
|
||||
|
||||
const EditableTable = ({ elements = [], data = [], cancel, save }) => {
|
||||
return (
|
||||
<Table>
|
||||
<THead>
|
||||
{elements.map(({ size, header }, idx) => (
|
||||
<Td header key={idx} size={size}>{header}</Td>
|
||||
))}
|
||||
</THead>
|
||||
<TBody>
|
||||
{data.map((it, idx) => <EditableRow key={idx} elements={elements} cancel={cancel} save={save} />)}
|
||||
</TBody>
|
||||
</Table>
|
||||
)
|
||||
}
|
||||
|
||||
export default EditableTable
|
||||
36
new-lamassu-admin/src/components/fake-table/Table.styles.js
Normal file
36
new-lamassu-admin/src/components/fake-table/Table.styles.js
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
import {
|
||||
tableHeaderColor,
|
||||
tableHeaderHeight,
|
||||
spacer,
|
||||
white
|
||||
} from '../styling/variables'
|
||||
|
||||
import typographyStyles from './typography/styles'
|
||||
|
||||
const { label2 } = typographyStyles
|
||||
|
||||
export default {
|
||||
tableBody: {
|
||||
borderSpacing: '0 4px'
|
||||
},
|
||||
header: {
|
||||
extend: label2,
|
||||
backgroundColor: tableHeaderColor,
|
||||
height: tableHeaderHeight,
|
||||
textAlign: 'left',
|
||||
color: white,
|
||||
// display: 'flex'
|
||||
display: 'table-row'
|
||||
},
|
||||
td: {
|
||||
padding: `0 ${spacer * 3}px`
|
||||
},
|
||||
tdHeader: {
|
||||
verticalAlign: 'middle',
|
||||
display: 'table-cell',
|
||||
padding: `0 ${spacer * 3}px`
|
||||
},
|
||||
summary: {
|
||||
cursor: 'auto'
|
||||
}
|
||||
}
|
||||
43
new-lamassu-admin/src/components/inputs/Checkbox.js
Normal file
43
new-lamassu-admin/src/components/inputs/Checkbox.js
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import React, { memo } from 'react'
|
||||
import { makeStyles } from '@material-ui/core/styles'
|
||||
import Checkbox from '@material-ui/core/Checkbox'
|
||||
import CheckBoxOutlineBlankIcon from '@material-ui/icons/CheckBoxOutlineBlank'
|
||||
import CheckBoxIcon from '@material-ui/icons/CheckBox'
|
||||
|
||||
import { secondaryColor } from '../../styling/variables'
|
||||
|
||||
const useStyles = makeStyles({
|
||||
root: {
|
||||
color: secondaryColor,
|
||||
'&$checked': {
|
||||
color: secondaryColor
|
||||
}
|
||||
},
|
||||
checked: {}
|
||||
})
|
||||
|
||||
const CheckboxInput = memo(({ label, ...props }) => {
|
||||
const classes = useStyles()
|
||||
|
||||
const { name, onChange, value } = props.field
|
||||
const { values, touched, errors } = props.form
|
||||
|
||||
return (
|
||||
<Checkbox
|
||||
id={name}
|
||||
classes={{
|
||||
root: classes.root,
|
||||
checked: classes.checked
|
||||
}}
|
||||
onChange={onChange}
|
||||
value={value}
|
||||
checked={value}
|
||||
icon={<CheckBoxOutlineBlankIcon style={{ marginLeft: 2, fontSize: 16 }} />}
|
||||
checkedIcon={<CheckBoxIcon style={{ fontSize: 20 }} />}
|
||||
disableRipple
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
||||
export default CheckboxInput
|
||||
14
new-lamassu-admin/src/components/inputs/Radio.js
Normal file
14
new-lamassu-admin/src/components/inputs/Radio.js
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import React from 'react'
|
||||
|
||||
function Radio ({ label, ...props }) {
|
||||
return (
|
||||
<>
|
||||
<label>
|
||||
<input type='radio' className='with-gap' name='gruop1' />
|
||||
<span>{label || ''}</span>
|
||||
</label>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Radio
|
||||
60
new-lamassu-admin/src/components/inputs/Select.js
Normal file
60
new-lamassu-admin/src/components/inputs/Select.js
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
import React from 'react'
|
||||
import { useSelect } from 'downshift'
|
||||
import { startCase } from 'lodash/fp'
|
||||
import classnames from 'classnames'
|
||||
|
||||
import { ReactComponent as Arrowdown } from '../../styling/icons/action/arrow/regular.svg'
|
||||
|
||||
import styles from './Select.styles'
|
||||
import { makeStyles } from '@material-ui/core/styles'
|
||||
|
||||
const useStyles = makeStyles(styles)
|
||||
|
||||
function Select ({ label, items, ...props }) {
|
||||
const classes = useStyles()
|
||||
|
||||
const {
|
||||
isOpen,
|
||||
selectedItem,
|
||||
getToggleButtonProps,
|
||||
getLabelProps,
|
||||
getMenuProps,
|
||||
getItemProps
|
||||
} = useSelect({
|
||||
items,
|
||||
selectedItem: props.selectedItem,
|
||||
onSelectedItemChange: item => {
|
||||
props.onSelectedItemChange(item.selectedItem)
|
||||
}
|
||||
})
|
||||
|
||||
const selectClassNames = {
|
||||
[classes.select]: true,
|
||||
[classes.selectFiltered]: selectedItem !== props.default,
|
||||
[classes.open]: isOpen
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classnames(selectClassNames)}>
|
||||
<label {...getLabelProps()}>{startCase(label)}</label>
|
||||
<button
|
||||
{...getToggleButtonProps()}
|
||||
>
|
||||
{startCase(selectedItem)} <Arrowdown />
|
||||
</button>
|
||||
<ul {...getMenuProps()}>
|
||||
{isOpen &&
|
||||
items.map((item, index) => (
|
||||
<li
|
||||
key={`${item}${index}`}
|
||||
{...getItemProps({ item, index })}
|
||||
>
|
||||
{startCase(item)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Select
|
||||
83
new-lamassu-admin/src/components/inputs/Select.styles.js
Normal file
83
new-lamassu-admin/src/components/inputs/Select.styles.js
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
import { zircon, comet, white, fontSecondary } from '../../styling/variables'
|
||||
import typographyStyles from '../typography/styles'
|
||||
|
||||
const { select, regularLabel, label, label2 } = typographyStyles
|
||||
|
||||
const WIDTH = 152
|
||||
|
||||
export default {
|
||||
select: {
|
||||
width: WIDTH,
|
||||
zIndex: 1000,
|
||||
'& label': {
|
||||
extend: regularLabel,
|
||||
color: comet,
|
||||
paddingLeft: 10
|
||||
},
|
||||
'& button': {
|
||||
extend: select,
|
||||
position: 'relative',
|
||||
border: 0,
|
||||
backgroundColor: zircon,
|
||||
width: WIDTH,
|
||||
padding: '6px 0 6px 12px',
|
||||
borderRadius: 20,
|
||||
lineHeight: '1.14',
|
||||
textAlign: 'left',
|
||||
color: comet,
|
||||
cursor: 'pointer',
|
||||
outline: '0 none'
|
||||
},
|
||||
'& ul': {
|
||||
maxHeight: '200px',
|
||||
width: WIDTH,
|
||||
overflowY: 'auto',
|
||||
position: 'absolute',
|
||||
margin: 0,
|
||||
borderTop: 0,
|
||||
padding: 0,
|
||||
borderRadius: '0 0 16px 16px',
|
||||
backgroundColor: zircon,
|
||||
outline: '0 none',
|
||||
'& li': {
|
||||
listStyleType: 'none',
|
||||
padding: '6px 0 6px 12px',
|
||||
cursor: 'pointer'
|
||||
},
|
||||
'& li:hover': {
|
||||
backgroundColor: comet,
|
||||
color: white
|
||||
}
|
||||
},
|
||||
'& svg': {
|
||||
position: 'absolute',
|
||||
top: 12,
|
||||
right: 14,
|
||||
fill: comet
|
||||
}
|
||||
},
|
||||
selectFiltered: {
|
||||
'& button': {
|
||||
backgroundColor: comet,
|
||||
color: white
|
||||
},
|
||||
'& ul': {
|
||||
'& li': {
|
||||
backgroundColor: comet,
|
||||
color: white
|
||||
},
|
||||
'& li:hover': {
|
||||
backgroundColor: zircon,
|
||||
color: comet
|
||||
}
|
||||
},
|
||||
'& svg': {
|
||||
fill: `${white} !important`
|
||||
}
|
||||
},
|
||||
open: {
|
||||
'& button': {
|
||||
borderRadius: '16px 16px 0 0'
|
||||
}
|
||||
}
|
||||
}
|
||||
73
new-lamassu-admin/src/components/inputs/Switch.js
Normal file
73
new-lamassu-admin/src/components/inputs/Switch.js
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
import React, { memo } from 'react'
|
||||
import { makeStyles } from '@material-ui/core/styles'
|
||||
import Switch from '@material-ui/core/Switch'
|
||||
|
||||
import { secondaryColor, offColor, disabledColor, disabledColor2 } from '../../styling/variables'
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
root: {
|
||||
width: 32,
|
||||
height: 20,
|
||||
padding: 0,
|
||||
margin: theme.spacing(1)
|
||||
},
|
||||
switchBase: {
|
||||
padding: 2,
|
||||
'&$disabled': {
|
||||
color: disabledColor2,
|
||||
'& + $track': {
|
||||
backgroundColor: disabledColor,
|
||||
opacity: 1
|
||||
}
|
||||
},
|
||||
'&$checked': {
|
||||
color: theme.palette.common.white,
|
||||
'& + $track': {
|
||||
backgroundColor: secondaryColor,
|
||||
opacity: 1,
|
||||
border: 'none'
|
||||
}
|
||||
},
|
||||
'&$focusVisible $thumb': {
|
||||
border: '6px solid #fff'
|
||||
}
|
||||
},
|
||||
thumb: {
|
||||
width: 16,
|
||||
height: 16
|
||||
},
|
||||
track: {
|
||||
borderRadius: 17,
|
||||
border: 'none',
|
||||
backgroundColor: offColor,
|
||||
opacity: 1,
|
||||
transition: theme.transitions.create(['background-color', 'border'])
|
||||
},
|
||||
disabled: {
|
||||
},
|
||||
checked: {
|
||||
},
|
||||
focusVisible: {
|
||||
}
|
||||
}))
|
||||
|
||||
const SwitchInput = memo(({ ...props }) => {
|
||||
const classes = useStyles()
|
||||
return (
|
||||
<Switch
|
||||
focusVisibleClassName={classes.focusVisible}
|
||||
disableRipple
|
||||
classes={{
|
||||
root: classes.root,
|
||||
switchBase: classes.switchBase,
|
||||
thumb: classes.thumb,
|
||||
track: classes.track,
|
||||
checked: classes.checked,
|
||||
disabled: classes.disabled
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
||||
export default SwitchInput
|
||||
51
new-lamassu-admin/src/components/inputs/TextInput.js
Normal file
51
new-lamassu-admin/src/components/inputs/TextInput.js
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
import React, { memo } from 'react'
|
||||
import TextField from '@material-ui/core/TextField'
|
||||
import InputAdornment from '@material-ui/core/InputAdornment'
|
||||
import { makeStyles } from '@material-ui/core/styles'
|
||||
|
||||
import { fontColor, inputFontSize, inputFontSizeLg, inputFontWeight } from '../../styling/variables'
|
||||
|
||||
const useStyles = makeStyles({
|
||||
inputRoot: {
|
||||
fontSize: inputFontSize,
|
||||
color: fontColor,
|
||||
fontWeight: inputFontWeight
|
||||
},
|
||||
inputRootLg: {
|
||||
fontSize: inputFontSizeLg,
|
||||
color: fontColor,
|
||||
fontWeight: inputFontWeight
|
||||
},
|
||||
labelRoot: {
|
||||
color: fontColor
|
||||
}
|
||||
})
|
||||
|
||||
const TextInput = memo(({ suffix, large, ...props }) => {
|
||||
const { name, onChange, onBlur, value } = props.field
|
||||
const { touched, errors } = props.form
|
||||
const classes = useStyles()
|
||||
|
||||
return (
|
||||
<TextField
|
||||
id={name}
|
||||
onChange={onChange}
|
||||
onBlur={onBlur}
|
||||
error={!!(touched[name] && errors[name])}
|
||||
value={value}
|
||||
classes={{ root: classes.root }}
|
||||
InputProps={{
|
||||
className: large ? classes.inputRootLg : classes.inputRoot,
|
||||
endAdornment: suffix ? (
|
||||
<InputAdornment className={classes.inputRoot} disableTypography position='end'>
|
||||
{suffix}
|
||||
</InputAdornment>
|
||||
) : null
|
||||
}}
|
||||
InputLabelProps={{ className: classes.labelRoot }}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
||||
export default TextInput
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
import Autocomplete from './autocomplete/Autocomplete'
|
||||
import AutocompleteMultiple from './autocomplete/AutocompleteMultiple'
|
||||
import Checkbox from './base/Checkbox'
|
||||
import Radio from './base/Radio'
|
||||
import TextInput from './base/TextInput'
|
||||
import Switch from './base/Switch'
|
||||
import Checkbox from './Checkbox'
|
||||
import Radio from './Radio'
|
||||
import TextInput from './TextInput'
|
||||
import Switch from './Switch'
|
||||
import Select from './Select'
|
||||
|
||||
export { Autocomplete, AutocompleteMultiple, TextInput, Radio, Checkbox, Switch }
|
||||
export { Autocomplete, AutocompleteMultiple, TextInput, Radio, Checkbox, Switch, Select }
|
||||
|
|
|
|||
|
|
@ -105,6 +105,11 @@ export default {
|
|||
fontFamily: fontSecondary,
|
||||
fontWeight: 700
|
||||
},
|
||||
select: {
|
||||
fontSize: fontSize3,
|
||||
fontFamily: fontSecondary,
|
||||
fontWeight: 500
|
||||
},
|
||||
inline: {
|
||||
display: 'inline'
|
||||
}
|
||||
|
|
|
|||
60
new-lamassu-admin/src/pages/Funding.module.scss
Normal file
60
new-lamassu-admin/src/pages/Funding.module.scss
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
$spacer: 8;
|
||||
$subheader-color: white;
|
||||
$placeholder-color: white;
|
||||
|
||||
.wrapper {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.main {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.firstSide {
|
||||
margin: 0 ($spacer * 8px) 0 ($spacer * 6px);
|
||||
}
|
||||
|
||||
.secondSide {
|
||||
margin-top: -49px;
|
||||
}
|
||||
|
||||
.coinTotal {
|
||||
margin: ($spacer * 1.5px) 0;
|
||||
}
|
||||
|
||||
.noMargin {
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
.leftSpacer {
|
||||
margin-left: $spacer * 1px;
|
||||
}
|
||||
|
||||
.topSpacer {
|
||||
margin-top: $spacer * 5px;
|
||||
}
|
||||
|
||||
.addressWrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
background-color: $subheader-color;
|
||||
}
|
||||
|
||||
.address {
|
||||
width: 375px;
|
||||
margin: ($spacer * 1.5px) ($spacer * 3px);
|
||||
}
|
||||
|
||||
.total {
|
||||
margin-top: auto;
|
||||
text-align: right;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.totalTitle {
|
||||
color: $placeholder-color;
|
||||
}
|
||||
12
new-lamassu-admin/src/pages/LogPageHeader.styles.js
Normal file
12
new-lamassu-admin/src/pages/LogPageHeader.styles.js
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
export default {
|
||||
titleAndButtonsContainer: {
|
||||
display: 'flex'
|
||||
},
|
||||
buttonsWrapper: {
|
||||
display: 'flex',
|
||||
marginLeft: 10,
|
||||
'& > *': {
|
||||
margin: 'auto 10px'
|
||||
}
|
||||
}
|
||||
}
|
||||
52
new-lamassu-admin/src/pages/Logs.module.scss
Normal file
52
new-lamassu-admin/src/pages/Logs.module.scss
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
.titleWrapper {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.tableWrapper {
|
||||
flex: 1;
|
||||
margin-left: 40px;
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
width: 100%;
|
||||
max-width: 78%;
|
||||
max-height: 70vh;
|
||||
}
|
||||
|
||||
.table {
|
||||
white-space: nowrap;
|
||||
display: block;
|
||||
& th {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.dateColumn {
|
||||
min-width: 160px;
|
||||
}
|
||||
|
||||
.levelColumn {
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
.fillColumn {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.button {
|
||||
margin: 8px;
|
||||
}
|
||||
|
||||
.buttonsWrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
200
new-lamassu-admin/src/pages/ServerLogs.js
Normal file
200
new-lamassu-admin/src/pages/ServerLogs.js
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
import React, { useState } from 'react'
|
||||
import FileSaver from 'file-saver'
|
||||
import { concat, uniq } from 'lodash/fp'
|
||||
import moment from 'moment'
|
||||
import useAxios from '@use-hooks/axios'
|
||||
|
||||
import Title from '../components/Title'
|
||||
import { Info3 } from '../components/typography'
|
||||
import { FeatureButton, SimpleButton } from '../components/buttons'
|
||||
import { Table, TableHead, TableRow, TableHeader, TableBody, TableCell } from '../components/table'
|
||||
import { Select } from '../components/inputs'
|
||||
import Uptime from '../components/Uptime'
|
||||
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 styles from './Logs.styles'
|
||||
import logPageHeaderStyles from './LogPageHeader.styles'
|
||||
|
||||
const { regularLabel } = typographyStyles
|
||||
const { tableWrapper } = styles
|
||||
const { titleAndButtonsContainer, buttonsWrapper } = logPageHeaderStyles
|
||||
|
||||
styles.titleWrapper = {
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between'
|
||||
}
|
||||
|
||||
styles.serverTableWrapper = {
|
||||
extend: tableWrapper,
|
||||
maxWidth: '100%',
|
||||
marginLeft: 0
|
||||
}
|
||||
|
||||
styles.serverVersion = {
|
||||
extend: regularLabel,
|
||||
color: comet,
|
||||
margin: 'auto 0 auto 0'
|
||||
}
|
||||
|
||||
styles.headerLine2 = {
|
||||
height: 60,
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
marginBottom: 24
|
||||
}
|
||||
|
||||
styles.uptimeContainer = {
|
||||
margin: 'auto 0 auto 0'
|
||||
}
|
||||
|
||||
styles.titleAndButtonsContainer = titleAndButtonsContainer
|
||||
styles.buttonsWrapper = buttonsWrapper
|
||||
|
||||
const useStyles = makeStyles(styles)
|
||||
|
||||
const SHOW_ALL = 'Show all'
|
||||
|
||||
const formatDate = date => {
|
||||
return moment(date).format('YYYY-MM-DD HH:mm')
|
||||
}
|
||||
|
||||
const Logs = () => {
|
||||
const [saveMessage, setSaveMessage] = useState(null)
|
||||
const [logLevel, setLogLevel] = useState(SHOW_ALL)
|
||||
const [version, setVersion] = useState(null)
|
||||
const [processStates, setProcessStates] = useState(null)
|
||||
|
||||
const classes = useStyles()
|
||||
|
||||
useAxios({
|
||||
url: 'http://localhost:8070/api/version',
|
||||
method: 'GET',
|
||||
trigger: [],
|
||||
customHandler: (err, res) => {
|
||||
if (err) return
|
||||
if (res) {
|
||||
setVersion(res.data)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
useAxios({
|
||||
url: 'http://localhost:8070/api/uptimes',
|
||||
method: 'GET',
|
||||
trigger: [],
|
||||
customHandler: (err, res) => {
|
||||
if (err) return
|
||||
if (res) {
|
||||
setProcessStates(res.data)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const { response: logsResponse } = useAxios({
|
||||
url: 'http://localhost:8070/api/server_logs/',
|
||||
method: 'GET',
|
||||
trigger: [],
|
||||
customHandler: () => {
|
||||
setSaveMessage('')
|
||||
}
|
||||
})
|
||||
|
||||
const { loading, reFetch: sendSnapshot } = useAxios({
|
||||
url: 'http://localhost:8070/api/server_support_logs',
|
||||
method: 'POST',
|
||||
customHandler: (err, res) => {
|
||||
if (err) {
|
||||
setSaveMessage('Failure saving snapshot')
|
||||
throw err
|
||||
}
|
||||
setSaveMessage('✓ Saved latest snapshot')
|
||||
}
|
||||
})
|
||||
|
||||
const handleLogLevelChange = (item) => setLogLevel(item)
|
||||
|
||||
const formatDateFile = date => {
|
||||
return moment(date).format('YYYY-MM-DD_HH-mm')
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={classes.titleWrapper}>
|
||||
<div className={classes.titleAndButtonsContainer}>
|
||||
<Title>Server</Title>
|
||||
{logsResponse && (
|
||||
<div className={classes.buttonsWrapper}>
|
||||
<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`)
|
||||
}}
|
||||
/>
|
||||
<SimpleButton className={classes.button} disabled={loading} onClick={sendSnapshot}>
|
||||
Share with Lamassu
|
||||
</SimpleButton>
|
||||
<Info3>{saveMessage}</Info3>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className={classes.serverVersion}>
|
||||
{version && (
|
||||
<span>Server version: v{version}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className={classes.headerLine2}>
|
||||
{logsResponse && (
|
||||
<Select
|
||||
onSelectedItemChange={handleLogLevelChange}
|
||||
label='Level'
|
||||
items={concat([SHOW_ALL], uniq(logsResponse.data.logs.map(log => log.logLevel)))}
|
||||
default={SHOW_ALL}
|
||||
selectedItem={logLevel}
|
||||
/>
|
||||
)}
|
||||
<div className={classes.uptimeContainer}>
|
||||
{processStates &&
|
||||
processStates.map((process, idx) => (
|
||||
<Uptime key={idx} process={process} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className={classes.wrapper}>
|
||||
<div className={classes.serverTableWrapper}>
|
||||
<Table className={classes.table}>
|
||||
<TableHead>
|
||||
<TableRow header>
|
||||
<TableHeader className={classes.dateColumn}>Date</TableHeader>
|
||||
<TableHeader className={classes.levelColumn}>Level</TableHeader>
|
||||
<TableHeader className={classes.fillColumn} />
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{logsResponse &&
|
||||
logsResponse.data.logs.filter(log => logLevel === SHOW_ALL || log.logLevel === logLevel).map((log, idx) => (
|
||||
<TableRow key={idx} size='sm'>
|
||||
<TableCell>{formatDate(log.timestamp)}</TableCell>
|
||||
<TableCell>{log.logLevel}</TableCell>
|
||||
<TableCell>{log.message}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Logs
|
||||
|
|
@ -6,6 +6,7 @@ import Commissions from '../pages/Commissions'
|
|||
import Logs from '../pages/Logs'
|
||||
import Locales from '../pages/Locales'
|
||||
import Funding from '../pages/Funding'
|
||||
import ServerLogs from '../pages/ServerLogs'
|
||||
|
||||
const tree = [
|
||||
{ key: 'transactions', label: 'Transactions', route: '/transactions' },
|
||||
|
|
@ -17,7 +18,8 @@ const tree = [
|
|||
route: '/maintenance',
|
||||
children: [
|
||||
{ key: 'logs', label: 'Logs', route: '/maintenance/logs' },
|
||||
{ key: 'fuding', label: 'Funding', route: '/maintenance/funding' }
|
||||
{ key: 'fuding', label: 'Funding', route: '/maintenance/funding' },
|
||||
{ key: 'server-logs', label: 'Server', route: '/maintenance/server-logs' }
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -58,6 +60,7 @@ const Routes = () => (
|
|||
<Route path='/settings/locale' component={Locales} />
|
||||
<Route path='/maintenance/logs' component={Logs} />
|
||||
<Route path='/maintenance/funding' component={Funding} />
|
||||
<Route path='/maintenance/server-logs' component={ServerLogs} />
|
||||
</Switch>
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -38,6 +38,9 @@ export default {
|
|||
a:active,
|
||||
a:hover`]: {
|
||||
outline: '0 none'
|
||||
},
|
||||
'button::-moz-focus-inner': {
|
||||
border: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
17
new-lamassu-admin/src/styling/icons/action/arrow/regular.svg
Normal file
17
new-lamassu-admin/src/styling/icons/action/arrow/regular.svg
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="13px" height="8px" viewBox="0 0 13 8" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 56.3 (81716) - https://sketch.com -->
|
||||
<title>icon/action/arrow/regular</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs>
|
||||
<path d="M5.3501239,7.53208616 L0.473798314,2.73082122 C-0.158421727,2.1051411 -0.158421727,1.0952488 0.476737158,0.466675069 C1.11220338,-0.155816755 2.1378971,-0.155816755 2.77494316,0.468226909 L6.49990857,4.13723769 L10.2264532,0.466675069 C10.8619195,-0.155816755 11.8876132,-0.155816755 12.5260183,0.469568675 C13.1582383,1.0952488 13.1582383,2.1051411 12.5245507,2.73226987 L7.64673876,7.53497972 C7.33802629,7.83583835 6.92590837,8 6.49990828,8 C6.0739082,8 5.66179027,7.83583835 5.3501239,7.53208616 Z" id="path-1"></path>
|
||||
</defs>
|
||||
<g id="Styleguide" stroke="none" stroke-width="1" fill-rule="evenodd">
|
||||
<g id="icon/action/arrow/regular">
|
||||
<mask id="mask-2" fill="white">
|
||||
<use xlink:href="#path-1"></use>
|
||||
</mask>
|
||||
<use id="Mask" fill-rule="nonzero" xlink:href="#path-1"></use>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
|
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="14px" height="13px" viewBox="0 0 14 13" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 56.3 (81716) - https://sketch.com -->
|
||||
<title>icon/button/download/white</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<g id="Styleguide" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
|
||||
<g id="icon/button/download/white" transform="translate(1.000000, 0.000000)" stroke="#FFFFFF">
|
||||
<g id="icon/sf-small/wizzard">
|
||||
<polyline id="Path-3" points="3.6 5.4 6 7.8 8.4 5.4"></polyline>
|
||||
<path d="M6,0.5 L6,7.4" id="Path-4"></path>
|
||||
<path d="M0,10 L0,10 C0,10.9942 0.8058,11.8 1.8,11.8 L10.2,11.8 C11.1942,11.8 12,10.9942 12,10" id="Stroke-1"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 933 B |
|
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="14px" height="13px" viewBox="0 0 14 13" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 56.3 (81716) - https://sketch.com -->
|
||||
<title>icon/button/download/zodiac</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<g id="Styleguide" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
|
||||
<g id="icon/button/download/zodiac" transform="translate(1.000000, 0.000000)" stroke="#1B2559">
|
||||
<g id="icon/sf-small/wizzard">
|
||||
<polyline id="Path-3" points="3.6 5.4 6 7.8 8.4 5.4"></polyline>
|
||||
<path d="M6,0.5 L6,7.4" id="Path-4"></path>
|
||||
<path d="M0,10 L0,10 C0,10.9942 0.8058,11.8 1.8,11.8 L10.2,11.8 C11.1942,11.8 12,10.9942 12,10" id="Stroke-1"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 935 B |
|
|
@ -111,6 +111,11 @@ export {
|
|||
white,
|
||||
zircon,
|
||||
zircon2,
|
||||
comet,
|
||||
spring2,
|
||||
spring3,
|
||||
tomato,
|
||||
mistyRose,
|
||||
|
||||
primaryColor,
|
||||
secondaryColor,
|
||||
|
|
|
|||
1606
package-lock.json
generated
1606
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -61,8 +61,10 @@
|
|||
"uuid": "^3.1.0",
|
||||
"web3": "^0.20.6",
|
||||
"winston": "^2.4.2",
|
||||
"winston-transport": "^4.3.0",
|
||||
"ws": "^3.1.0",
|
||||
"xml-stream": "^0.4.5"
|
||||
"xml-stream": "^0.4.5",
|
||||
"xmlrpc": "^1.3.2"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
|
|||
14
shell.nix
Normal file
14
shell.nix
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
with import <nixpkgs> {};
|
||||
|
||||
stdenv.mkDerivation {
|
||||
name = "node";
|
||||
buildInputs = [
|
||||
nodejs-8_x
|
||||
python2Full
|
||||
openssl_1_0_2
|
||||
postgresql_9_6
|
||||
];
|
||||
shellHook = ''
|
||||
export PATH="$HOME/.local:$PWD/node_modules/.bin/:$PATH"
|
||||
'';
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue