relint!
This commit is contained in:
parent
ebe6105fb3
commit
e33f0fa558
8 changed files with 131 additions and 124 deletions
|
@ -1,12 +1,12 @@
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react'
|
||||||
|
|
||||||
import './App.css';
|
import './App.css'
|
||||||
import Service from './Service.js';
|
import Service from './Service.js'
|
||||||
import ServicePlaceholder from './ServicePlaceholder.js';
|
import ServicePlaceholder from './ServicePlaceholder.js'
|
||||||
import OP from '../ws/op.js';
|
import OP from '../ws/op.js'
|
||||||
import { log } from '../util.js';
|
import { log } from '../util.js'
|
||||||
|
|
||||||
const DOMAIN = 'https://elstatus.stayathomeserver.club';
|
const DOMAIN = 'https://elstatus.stayathomeserver.club'
|
||||||
// const ENDPOINT = 'http://localhost:8069/api/status'
|
// const ENDPOINT = 'http://localhost:8069/api/status'
|
||||||
|
|
||||||
export default class App extends Component {
|
export default class App extends Component {
|
||||||
|
@ -20,101 +20,101 @@ export default class App extends Component {
|
||||||
metrics: null,
|
metrics: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
async componentDidMount() {
|
async componentDidMount () {
|
||||||
await this.loadMetrics();
|
await this.loadMetrics()
|
||||||
this.connect();
|
this.connect()
|
||||||
}
|
}
|
||||||
|
|
||||||
subscribeToChannels() {
|
subscribeToChannels () {
|
||||||
const channels = Object.keys(this.state.metrics.graph).map((channel) => `latency:${channel}`);
|
const channels = Object.keys(this.state.metrics.graph).map((channel) => `latency:${channel}`)
|
||||||
log('subscribing to channels:', channels);
|
log('subscribing to channels:', channels)
|
||||||
|
|
||||||
this._send({
|
this._send({
|
||||||
op: OP.SUBSCRIBE,
|
op: OP.SUBSCRIBE,
|
||||||
channels,
|
channels,
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePacket(packet) {
|
handlePacket (packet) {
|
||||||
const { op, c: channel, d: data } = packet;
|
const { op, c: channel, d: data } = packet
|
||||||
|
|
||||||
if (op !== OP.DATA) {
|
if (op !== OP.DATA) {
|
||||||
log('ignoring boring packet:', packet);
|
log('ignoring boring packet:', packet)
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const [, name] = channel.split(':');
|
const [, name] = channel.split(':')
|
||||||
|
|
||||||
log('updating from channel:', channel);
|
log('updating from channel:', channel)
|
||||||
|
|
||||||
const { metrics } = this.state;
|
const { metrics } = this.state
|
||||||
const graph = metrics.graph[name].slice(1);
|
const graph = metrics.graph[name].slice(1)
|
||||||
const newGraph = [data, ...graph];
|
const newGraph = [data, ...graph]
|
||||||
|
|
||||||
log('adding data:', data);
|
log('adding data:', data)
|
||||||
|
|
||||||
this.setState(({ metrics: oldMetrics }, _props) => {
|
this.setState(({ metrics: oldMetrics }, _props) => {
|
||||||
const newMetrics = { ...oldMetrics };
|
const newMetrics = { ...oldMetrics }
|
||||||
newMetrics.graph[name] = newGraph;
|
newMetrics.graph[name] = newGraph
|
||||||
|
|
||||||
const [, latency] = data;
|
const [, latency] = data
|
||||||
newMetrics.status[name].latency = latency;
|
newMetrics.status[name].latency = latency
|
||||||
|
|
||||||
return {
|
return {
|
||||||
metrics: newMetrics,
|
metrics: newMetrics,
|
||||||
};
|
}
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
connect() {
|
connect () {
|
||||||
log('connecting to ws');
|
log('connecting to ws')
|
||||||
|
|
||||||
const endpoint = (`${DOMAIN}/api/streaming`).replace('https', 'wss');
|
const endpoint = (`${DOMAIN}/api/streaming`).replace('https', 'wss')
|
||||||
this.websocket = new WebSocket(endpoint);
|
this.websocket = new WebSocket(endpoint)
|
||||||
|
|
||||||
this.websocket.onopen = () => {
|
this.websocket.onopen = () => {
|
||||||
log('ws opened');
|
log('ws opened')
|
||||||
this.subscribeToChannels();
|
this.subscribeToChannels()
|
||||||
};
|
}
|
||||||
|
|
||||||
this.websocket.onclose = ({ code, reason }) => {
|
this.websocket.onclose = ({ code, reason }) => {
|
||||||
log(`ws closed with code ${code} (reason: ${reason || '<none>'}); `
|
log(`ws closed with code ${code} (reason: ${reason || '<none>'}); ` +
|
||||||
+ `attempting to reconnect in ${this.reconnectionTime}ms`);
|
`attempting to reconnect in ${this.reconnectionTime}ms`)
|
||||||
setTimeout(() => this.connect(), this.reconnectionTime);
|
setTimeout(() => this.connect(), this.reconnectionTime)
|
||||||
this.reconnectionTime *= 2;
|
this.reconnectionTime *= 2
|
||||||
};
|
}
|
||||||
|
|
||||||
this.websocket.onmessage = (message) => {
|
this.websocket.onmessage = (message) => {
|
||||||
const { data } = message;
|
const { data } = message
|
||||||
const parsed = JSON.parse(data);
|
const parsed = JSON.parse(data)
|
||||||
log('ws recv:', parsed);
|
log('ws recv:', parsed)
|
||||||
|
|
||||||
this.handlePacket(parsed);
|
this.handlePacket(parsed)
|
||||||
};
|
}
|
||||||
|
|
||||||
this.websocket.onerror = (event) => {
|
this.websocket.onerror = (event) => {
|
||||||
log('ws error:', event);
|
log('ws error:', event)
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
_send(payload) {
|
|
||||||
this.websocket.send(JSON.stringify(payload));
|
|
||||||
}
|
|
||||||
|
|
||||||
async loadMetrics() {
|
|
||||||
log('loading metrics');
|
|
||||||
|
|
||||||
try {
|
|
||||||
const resp = await fetch(`${DOMAIN}/api/status`);
|
|
||||||
const json = await resp.json();
|
|
||||||
|
|
||||||
this.setState({ metrics: json, loading: false });
|
|
||||||
} catch (err) {
|
|
||||||
this.setState({ error: err.toString() });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
_send (payload) {
|
||||||
|
this.websocket.send(JSON.stringify(payload))
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadMetrics () {
|
||||||
|
log('loading metrics')
|
||||||
|
|
||||||
|
try {
|
||||||
|
const resp = await fetch(`${DOMAIN}/api/status`)
|
||||||
|
const json = await resp.json()
|
||||||
|
|
||||||
|
this.setState({ metrics: json, loading: false })
|
||||||
|
} catch (err) {
|
||||||
|
this.setState({ error: err.toString() })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
const metrics = !this.state.metrics ? null : (
|
const metrics = !this.state.metrics ? null : (
|
||||||
<div className="services">
|
<div className="services">
|
||||||
{Object.entries(this.state.metrics.status)
|
{Object.entries(this.state.metrics.status)
|
||||||
|
@ -128,7 +128,7 @@ export default class App extends Component {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="dashboard">
|
<div className="dashboard">
|
||||||
|
@ -148,6 +148,6 @@ export default class App extends Component {
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
) : metrics}
|
) : metrics}
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react'
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
import ms from 'ms';
|
import ms from 'ms'
|
||||||
import {
|
import {
|
||||||
ResponsiveContainer,
|
ResponsiveContainer,
|
||||||
AreaChart,
|
AreaChart,
|
||||||
|
@ -11,9 +11,9 @@ import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Area,
|
Area,
|
||||||
ReferenceLine,
|
ReferenceLine,
|
||||||
} from 'recharts';
|
} from 'recharts'
|
||||||
|
|
||||||
import './Graph.css';
|
import './Graph.css'
|
||||||
|
|
||||||
export default class Graph extends Component {
|
export default class Graph extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
@ -24,34 +24,47 @@ export default class Graph extends Component {
|
||||||
screenWidth: window.innerWidth,
|
screenWidth: window.innerWidth,
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount () {
|
||||||
window.addEventListener('resize', this.handleScreenChange);
|
window.addEventListener('resize', this.handleScreenChange)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount () {
|
||||||
window.removeEventListener('resize', this.handleScreenChange);
|
window.removeEventListener('resize', this.handleScreenChange)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleScreenChange = () => {
|
handleScreenChange = () => {
|
||||||
this.setState({
|
this.setState({
|
||||||
screenWidth: window.innerWidth,
|
screenWidth: window.innerWidth,
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
processData() {
|
processData () {
|
||||||
const { data } = this.props;
|
const { data } = this.props
|
||||||
|
|
||||||
return data.map(([timestamp, latency]) => ({
|
const objects = data.map(([timestamp, latency]) => ({
|
||||||
timestamp,
|
timestamp,
|
||||||
latency,
|
latency,
|
||||||
})).sort(({ timestamp: a }, { timestamp: b }) => a - b);
|
}))
|
||||||
|
|
||||||
|
// sort so that new entries are first
|
||||||
|
return objects.sort(({ timestamp: a }, { timestamp: b }) => a - b)
|
||||||
}
|
}
|
||||||
|
|
||||||
isSmallScreen() {
|
isSmallScreen () {
|
||||||
return this.state.screenWidth < 500;
|
return this.state.screenWidth < 500
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render () {
|
||||||
|
const yAxis = this.isSmallScreen()
|
||||||
|
? null
|
||||||
|
: (
|
||||||
|
<YAxis
|
||||||
|
dataKey="latency"
|
||||||
|
tickLine={false}
|
||||||
|
tickFormatter={(tick) => `${tick}ms`}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="graph-container">
|
<div className="graph-container">
|
||||||
<ResponsiveContainer width="100%" height={175}>
|
<ResponsiveContainer width="100%" height={175}>
|
||||||
|
@ -63,14 +76,8 @@ export default class Graph extends Component {
|
||||||
tickFormatter={(tick) => ms(Date.now() - tick)}
|
tickFormatter={(tick) => ms(Date.now() - tick)}
|
||||||
tickLine={false}
|
tickLine={false}
|
||||||
/>
|
/>
|
||||||
{this.isSmallScreen()
|
{yAxis}
|
||||||
? null
|
|
||||||
: <YAxis
|
|
||||||
dataKey="latency"
|
|
||||||
tickLine={false}
|
|
||||||
tickFormatter={(tick) => `${tick}ms`}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
<CartesianGrid strokeDasharray="1 1" />
|
<CartesianGrid strokeDasharray="1 1" />
|
||||||
<Tooltip
|
<Tooltip
|
||||||
isAnimationActive={false}
|
isAnimationActive={false}
|
||||||
|
@ -94,6 +101,6 @@ export default class Graph extends Component {
|
||||||
</AreaChart>
|
</AreaChart>
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import React from 'react';
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||||
|
|
||||||
import './Service.css';
|
import './Service.css'
|
||||||
import Graph from './Graph.js';
|
import Graph from './Graph.js'
|
||||||
|
|
||||||
const Service = ({ graph, name, status, latency, description }) => (
|
const Service = ({ graph, name, status, latency, description }) => (
|
||||||
<div className="service">
|
<div className="service">
|
||||||
|
@ -28,12 +28,12 @@ const Service = ({ graph, name, status, latency, description }) => (
|
||||||
</p>
|
</p>
|
||||||
{graph ? <Graph data={graph} /> : null}
|
{graph ? <Graph data={graph} /> : null}
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
|
|
||||||
Service.defaultProps = {
|
Service.defaultProps = {
|
||||||
graph: null,
|
graph: null,
|
||||||
latency: null,
|
latency: null,
|
||||||
};
|
}
|
||||||
|
|
||||||
Service.propTypes = {
|
Service.propTypes = {
|
||||||
graph: PropTypes.arrayOf(
|
graph: PropTypes.arrayOf(
|
||||||
|
@ -43,6 +43,6 @@ Service.propTypes = {
|
||||||
status: PropTypes.bool.isRequired,
|
status: PropTypes.bool.isRequired,
|
||||||
latency: PropTypes.number,
|
latency: PropTypes.number,
|
||||||
description: PropTypes.string.isRequired,
|
description: PropTypes.string.isRequired,
|
||||||
};
|
}
|
||||||
|
|
||||||
export default Service;
|
export default Service
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import React from 'react';
|
import React from 'react'
|
||||||
|
|
||||||
import ReactPlaceholder from 'react-placeholder';
|
import ReactPlaceholder from 'react-placeholder'
|
||||||
import 'react-placeholder/lib/reactPlaceholder.css';
|
import 'react-placeholder/lib/reactPlaceholder.css'
|
||||||
|
|
||||||
import './ServicePlaceholder.css';
|
import './ServicePlaceholder.css'
|
||||||
|
|
||||||
const ServicePlaceholder = () => (
|
const ServicePlaceholder = () => (
|
||||||
<div className="service-placeholder">
|
<div className="service-placeholder">
|
||||||
|
@ -44,6 +44,6 @@ const ServicePlaceholder = () => (
|
||||||
{' '}
|
{' '}
|
||||||
</ReactPlaceholder>
|
</ReactPlaceholder>
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
|
|
||||||
export default ServicePlaceholder;
|
export default ServicePlaceholder
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import { library } from '@fortawesome/fontawesome-svg-core';
|
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||||
import {
|
import {
|
||||||
faCheckCircle,
|
faCheckCircle,
|
||||||
faExclamationCircle,
|
faExclamationCircle,
|
||||||
} from '@fortawesome/free-solid-svg-icons';
|
} from '@fortawesome/free-solid-svg-icons'
|
||||||
|
|
||||||
export default function register() {
|
export default function register () {
|
||||||
library.add(
|
library.add(
|
||||||
faCheckCircle,
|
faCheckCircle,
|
||||||
faExclamationCircle,
|
faExclamationCircle,
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import React from 'react';
|
import React from 'react'
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom'
|
||||||
|
|
||||||
import './index.css';
|
import './index.css'
|
||||||
import App from './components/App';
|
import App from './components/App'
|
||||||
import register from './icons.js';
|
import register from './icons.js'
|
||||||
|
|
||||||
register();
|
register()
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<App />,
|
<App />,
|
||||||
document.getElementById('root'),
|
document.getElementById('root'),
|
||||||
);
|
)
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
export function log(...args) {
|
export function log (...args) {
|
||||||
console.log(
|
console.log(
|
||||||
'%c[elstat]%c',
|
'%c[elstat]%c',
|
||||||
'color: purple; font-weight: bold',
|
'color: purple; font-weight: bold',
|
||||||
'color: inherit; font-weight: inherit',
|
'color: inherit; font-weight: inherit',
|
||||||
...args,
|
...args,
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
module.exports = {
|
export default {
|
||||||
UNSUBSCRIBE: -1,
|
UNSUBSCRIBE: -1,
|
||||||
SUBSCRIBE: 0,
|
SUBSCRIBE: 0,
|
||||||
SUBSCRIBED: 1,
|
SUBSCRIBED: 1,
|
||||||
UNSUBSCRIBED: 2,
|
UNSUBSCRIBED: 2,
|
||||||
DATA: 3,
|
DATA: 3,
|
||||||
};
|
}
|
||||||
|
|
Loading…
Reference in a new issue