Merge branch 'master' of gitlab.com:elixire/elstat
This commit is contained in:
commit
8d8f38b5a1
8 changed files with 204 additions and 5335 deletions
5357
priv/frontend/package-lock.json
generated
5357
priv/frontend/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -1,20 +1,19 @@
|
|||
{
|
||||
"name": "elstatus",
|
||||
"version": "0.1.0",
|
||||
"version": "0.0.0",
|
||||
"description": "react web application to summon lucifer",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.0-14",
|
||||
"@fortawesome/free-solid-svg-icons": "^5.1.0-11",
|
||||
"@fortawesome/react-fontawesome": "0.1.0-11",
|
||||
"@nivo/line": "^0.42.1",
|
||||
"i": "^0.3.6",
|
||||
"ms": "^2.1.1",
|
||||
"npm": "^6.1.0",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^16.4.0",
|
||||
"react-dom": "^16.4.0",
|
||||
"react-placeholder": "^3.0.1",
|
||||
"react-scripts": "1.1.4"
|
||||
"react-scripts": "1.1.4",
|
||||
"recharts": "^1.0.1"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
|
|
|
@ -77,8 +77,9 @@ export default class App extends Component {
|
|||
this.subscribeToChannels();
|
||||
};
|
||||
|
||||
this.websocket.onclose = () => {
|
||||
log(`ws closed; attempting to reconnect in ${this.reconnectionTime}ms`);
|
||||
this.websocket.onclose = ({ code, reason }) => {
|
||||
log(`ws closed with code ${code} (reason: ${reason || '<none>'}); `
|
||||
+ `attempting to reconnect in ${this.reconnectionTime}ms`);
|
||||
setTimeout(() => this.connect(), this.reconnectionTime);
|
||||
this.reconnectionTime *= 2;
|
||||
};
|
||||
|
@ -91,12 +92,11 @@ export default class App extends Component {
|
|||
this.handlePacket(parsed);
|
||||
};
|
||||
|
||||
this.websocket.onerror = (error) => {
|
||||
log('ws error:', error);
|
||||
this.websocket.onerror = (event) => {
|
||||
log('ws error:', event);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
_send(payload) {
|
||||
this.websocket.send(JSON.stringify(payload));
|
||||
}
|
||||
|
@ -140,7 +140,7 @@ export default class App extends Component {
|
|||
</div>
|
||||
) : null}
|
||||
|
||||
{this.state.loading ? (
|
||||
{this.state.loading && !this.state.error ? (
|
||||
<React.Fragment>
|
||||
<ServicePlaceholder />
|
||||
<ServicePlaceholder />
|
||||
|
|
|
@ -1,4 +1,22 @@
|
|||
.graph-container {
|
||||
width: 100%;
|
||||
height: 10rem;
|
||||
}
|
||||
|
||||
.recharts-cartesian-axis-tick-value {
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
.recharts-tooltip-wrapper .recharts-default-tooltip {
|
||||
padding: 0 0.5em !important;
|
||||
border-radius: 0.2rem;
|
||||
font-size: 0.85em;
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.recharts-default-tooltip li {
|
||||
color: inherit !important;
|
||||
}
|
||||
|
||||
.recharts-tooltip-item-value {
|
||||
font-family: PragmataPro, Menlo, "DejaVu Sans Mono", monospace;
|
||||
}
|
||||
|
|
|
@ -1,71 +1,98 @@
|
|||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import ms from 'ms';
|
||||
import { ResponsiveLine } from '@nivo/line';
|
||||
import {
|
||||
ResponsiveContainer,
|
||||
AreaChart,
|
||||
XAxis,
|
||||
YAxis,
|
||||
CartesianGrid,
|
||||
Tooltip,
|
||||
Area,
|
||||
ReferenceLine,
|
||||
} from 'recharts';
|
||||
|
||||
import './Graph.css';
|
||||
|
||||
export default class Graph extends Component {
|
||||
static propTypes = {
|
||||
data: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.number)).isRequired,
|
||||
}
|
||||
|
||||
state = {
|
||||
screenWidth: window.innerWidth,
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
window.addEventListener('resize', this.handleScreenChange);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
window.removeEventListener('resize', this.handleScreenChange);
|
||||
}
|
||||
|
||||
handleScreenChange = () => {
|
||||
this.setState({
|
||||
screenWidth: window.innerWidth,
|
||||
});
|
||||
}
|
||||
|
||||
processData() {
|
||||
const { data: unprocessedData } = this.props;
|
||||
const { data } = this.props;
|
||||
|
||||
const data = unprocessedData.map(([timestamp, latency]) => ({
|
||||
x: timestamp,
|
||||
y: latency,
|
||||
})).reverse();
|
||||
|
||||
return [
|
||||
{
|
||||
id: 'latency',
|
||||
color: 'hsl(220, 100%, 75%)',
|
||||
data,
|
||||
},
|
||||
];
|
||||
return data.map(([timestamp, latency]) => ({
|
||||
timestamp,
|
||||
latency,
|
||||
})).sort(({ timestamp: a }, { timestamp: b }) => a - b);
|
||||
}
|
||||
|
||||
isSmallScreen() {
|
||||
return window.innerWidth < 500;
|
||||
return this.state.screenWidth < 500;
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="graph-container">
|
||||
<ResponsiveLine
|
||||
data={this.processData()}
|
||||
margin={{ top: 30, left: 70, bottom: 50 }}
|
||||
maxY="auto"
|
||||
curve="monotoneX"
|
||||
|
||||
tooltipFormat={(d) => `${d}ms`}
|
||||
|
||||
axisLeft={{
|
||||
format: (d) => `${d}ms`,
|
||||
tickCount: 3,
|
||||
legend: 'latency',
|
||||
legendPosition: 'center',
|
||||
legendOffset: -55,
|
||||
tickSize: 0,
|
||||
}}
|
||||
|
||||
axisBottom={{
|
||||
format: (epoch) => {
|
||||
const interval = this.isSmallScreen() ? 7 : 5;
|
||||
const minutesAgo = Math.floor((Date.now() - epoch) / (1000 * 60));
|
||||
if (minutesAgo % interval !== 0 || minutesAgo === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return ms(Date.now() - epoch);
|
||||
},
|
||||
tickSize: 0,
|
||||
legend: 'time ago',
|
||||
legendPosition: 'center',
|
||||
legendOffset: 40,
|
||||
}}
|
||||
|
||||
enableDots={false}
|
||||
enableArea
|
||||
/>
|
||||
<ResponsiveContainer width="100%" height={175}>
|
||||
<AreaChart
|
||||
data={this.processData()}
|
||||
>
|
||||
<XAxis
|
||||
dataKey="timestamp"
|
||||
tickFormatter={(tick) => ms(Date.now() - tick)}
|
||||
tickLine={false}
|
||||
/>
|
||||
{this.isSmallScreen()
|
||||
? null
|
||||
: <YAxis
|
||||
dataKey="latency"
|
||||
tickLine={false}
|
||||
tickFormatter={(tick) => `${tick}ms`}
|
||||
/>
|
||||
}
|
||||
<CartesianGrid strokeDasharray="1 1" />
|
||||
<Tooltip
|
||||
isAnimationActive={false}
|
||||
formatter={(value) => `${value}ms`}
|
||||
label="DAB"
|
||||
separator=": "
|
||||
labelFormatter={() => null}
|
||||
/>
|
||||
<ReferenceLine
|
||||
y={1000}
|
||||
label="1s"
|
||||
stroke="pink"
|
||||
/>
|
||||
<Area
|
||||
type="monotone"
|
||||
dataKey="latency"
|
||||
stroke="hsla(200, 100%, 55%, 1)"
|
||||
fill="hsl(200, 100%, 85%)"
|
||||
isAnimationActive={false}
|
||||
/>
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -16,6 +16,12 @@
|
|||
color: hsl(0, 0%, 45%);
|
||||
}
|
||||
|
||||
@media (max-width: 500px) {
|
||||
.service .latency {
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.service .emoji {
|
||||
font-size: 2em;
|
||||
margin-right: 0.5em;
|
||||
|
|
|
@ -18,7 +18,7 @@ const Service = ({ graph, name, status, latency, description }) => (
|
|||
<h2 className="title">
|
||||
{name} {latency ? (
|
||||
<span className="latency">
|
||||
{latency}ms
|
||||
{Math.round(latency)}ms
|
||||
</span>
|
||||
) : null}
|
||||
</h2>
|
||||
|
@ -26,7 +26,7 @@ const Service = ({ graph, name, status, latency, description }) => (
|
|||
<p className="description">
|
||||
{description}
|
||||
</p>
|
||||
{graph ? <Graph width={500} height={175} data={graph} /> : null}
|
||||
{graph ? <Graph data={graph} /> : null}
|
||||
</div>
|
||||
);
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ const ServicePlaceholder = () => (
|
|||
<ReactPlaceholder
|
||||
type="rect"
|
||||
ready={false}
|
||||
style={{ width: '100%', height: '6rem', marginTop: '1rem' }}
|
||||
style={{ width: '100%', height: '175px', marginTop: '1rem' }}
|
||||
showLoadingAnimation
|
||||
>
|
||||
{' '}
|
||||
|
|
Loading…
Reference in a new issue