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",
|
"name": "elstatus",
|
||||||
"version": "0.1.0",
|
"version": "0.0.0",
|
||||||
|
"description": "react web application to summon lucifer",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fortawesome/fontawesome-svg-core": "^1.2.0-14",
|
"@fortawesome/fontawesome-svg-core": "^1.2.0-14",
|
||||||
"@fortawesome/free-solid-svg-icons": "^5.1.0-11",
|
"@fortawesome/free-solid-svg-icons": "^5.1.0-11",
|
||||||
"@fortawesome/react-fontawesome": "0.1.0-11",
|
"@fortawesome/react-fontawesome": "0.1.0-11",
|
||||||
"@nivo/line": "^0.42.1",
|
|
||||||
"i": "^0.3.6",
|
|
||||||
"ms": "^2.1.1",
|
"ms": "^2.1.1",
|
||||||
"npm": "^6.1.0",
|
|
||||||
"prop-types": "^15.6.2",
|
"prop-types": "^15.6.2",
|
||||||
"react": "^16.4.0",
|
"react": "^16.4.0",
|
||||||
"react-dom": "^16.4.0",
|
"react-dom": "^16.4.0",
|
||||||
"react-placeholder": "^3.0.1",
|
"react-placeholder": "^3.0.1",
|
||||||
"react-scripts": "1.1.4"
|
"react-scripts": "1.1.4",
|
||||||
|
"recharts": "^1.0.1"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "react-scripts start",
|
"start": "react-scripts start",
|
||||||
|
|
|
@ -77,8 +77,9 @@ export default class App extends Component {
|
||||||
this.subscribeToChannels();
|
this.subscribeToChannels();
|
||||||
};
|
};
|
||||||
|
|
||||||
this.websocket.onclose = () => {
|
this.websocket.onclose = ({ code, reason }) => {
|
||||||
log(`ws closed; attempting to reconnect in ${this.reconnectionTime}ms`);
|
log(`ws closed with code ${code} (reason: ${reason || '<none>'}); `
|
||||||
|
+ `attempting to reconnect in ${this.reconnectionTime}ms`);
|
||||||
setTimeout(() => this.connect(), this.reconnectionTime);
|
setTimeout(() => this.connect(), this.reconnectionTime);
|
||||||
this.reconnectionTime *= 2;
|
this.reconnectionTime *= 2;
|
||||||
};
|
};
|
||||||
|
@ -91,12 +92,11 @@ export default class App extends Component {
|
||||||
this.handlePacket(parsed);
|
this.handlePacket(parsed);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.websocket.onerror = (error) => {
|
this.websocket.onerror = (event) => {
|
||||||
log('ws error:', error);
|
log('ws error:', event);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
_send(payload) {
|
_send(payload) {
|
||||||
this.websocket.send(JSON.stringify(payload));
|
this.websocket.send(JSON.stringify(payload));
|
||||||
}
|
}
|
||||||
|
@ -140,7 +140,7 @@ export default class App extends Component {
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{this.state.loading ? (
|
{this.state.loading && !this.state.error ? (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<ServicePlaceholder />
|
<ServicePlaceholder />
|
||||||
<ServicePlaceholder />
|
<ServicePlaceholder />
|
||||||
|
|
|
@ -1,4 +1,22 @@
|
||||||
.graph-container {
|
.graph-container {
|
||||||
width: 100%;
|
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 React, { Component } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import ms from 'ms';
|
import ms from 'ms';
|
||||||
import { ResponsiveLine } from '@nivo/line';
|
import {
|
||||||
|
ResponsiveContainer,
|
||||||
|
AreaChart,
|
||||||
|
XAxis,
|
||||||
|
YAxis,
|
||||||
|
CartesianGrid,
|
||||||
|
Tooltip,
|
||||||
|
Area,
|
||||||
|
ReferenceLine,
|
||||||
|
} from 'recharts';
|
||||||
|
|
||||||
import './Graph.css';
|
import './Graph.css';
|
||||||
|
|
||||||
export default class Graph extends Component {
|
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() {
|
processData() {
|
||||||
const { data: unprocessedData } = this.props;
|
const { data } = this.props;
|
||||||
|
|
||||||
const data = unprocessedData.map(([timestamp, latency]) => ({
|
return data.map(([timestamp, latency]) => ({
|
||||||
x: timestamp,
|
timestamp,
|
||||||
y: latency,
|
latency,
|
||||||
})).reverse();
|
})).sort(({ timestamp: a }, { timestamp: b }) => a - b);
|
||||||
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
id: 'latency',
|
|
||||||
color: 'hsl(220, 100%, 75%)',
|
|
||||||
data,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
isSmallScreen() {
|
isSmallScreen() {
|
||||||
return window.innerWidth < 500;
|
return this.state.screenWidth < 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className="graph-container">
|
<div className="graph-container">
|
||||||
<ResponsiveLine
|
<ResponsiveContainer width="100%" height={175}>
|
||||||
data={this.processData()}
|
<AreaChart
|
||||||
margin={{ top: 30, left: 70, bottom: 50 }}
|
data={this.processData()}
|
||||||
maxY="auto"
|
>
|
||||||
curve="monotoneX"
|
<XAxis
|
||||||
|
dataKey="timestamp"
|
||||||
tooltipFormat={(d) => `${d}ms`}
|
tickFormatter={(tick) => ms(Date.now() - tick)}
|
||||||
|
tickLine={false}
|
||||||
axisLeft={{
|
/>
|
||||||
format: (d) => `${d}ms`,
|
{this.isSmallScreen()
|
||||||
tickCount: 3,
|
? null
|
||||||
legend: 'latency',
|
: <YAxis
|
||||||
legendPosition: 'center',
|
dataKey="latency"
|
||||||
legendOffset: -55,
|
tickLine={false}
|
||||||
tickSize: 0,
|
tickFormatter={(tick) => `${tick}ms`}
|
||||||
}}
|
/>
|
||||||
|
}
|
||||||
axisBottom={{
|
<CartesianGrid strokeDasharray="1 1" />
|
||||||
format: (epoch) => {
|
<Tooltip
|
||||||
const interval = this.isSmallScreen() ? 7 : 5;
|
isAnimationActive={false}
|
||||||
const minutesAgo = Math.floor((Date.now() - epoch) / (1000 * 60));
|
formatter={(value) => `${value}ms`}
|
||||||
if (minutesAgo % interval !== 0 || minutesAgo === 0) {
|
label="DAB"
|
||||||
return undefined;
|
separator=": "
|
||||||
}
|
labelFormatter={() => null}
|
||||||
|
/>
|
||||||
return ms(Date.now() - epoch);
|
<ReferenceLine
|
||||||
},
|
y={1000}
|
||||||
tickSize: 0,
|
label="1s"
|
||||||
legend: 'time ago',
|
stroke="pink"
|
||||||
legendPosition: 'center',
|
/>
|
||||||
legendOffset: 40,
|
<Area
|
||||||
}}
|
type="monotone"
|
||||||
|
dataKey="latency"
|
||||||
enableDots={false}
|
stroke="hsla(200, 100%, 55%, 1)"
|
||||||
enableArea
|
fill="hsl(200, 100%, 85%)"
|
||||||
/>
|
isAnimationActive={false}
|
||||||
|
/>
|
||||||
|
</AreaChart>
|
||||||
|
</ResponsiveContainer>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,12 @@
|
||||||
color: hsl(0, 0%, 45%);
|
color: hsl(0, 0%, 45%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 500px) {
|
||||||
|
.service .latency {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.service .emoji {
|
.service .emoji {
|
||||||
font-size: 2em;
|
font-size: 2em;
|
||||||
margin-right: 0.5em;
|
margin-right: 0.5em;
|
||||||
|
|
|
@ -18,7 +18,7 @@ const Service = ({ graph, name, status, latency, description }) => (
|
||||||
<h2 className="title">
|
<h2 className="title">
|
||||||
{name} {latency ? (
|
{name} {latency ? (
|
||||||
<span className="latency">
|
<span className="latency">
|
||||||
{latency}ms
|
{Math.round(latency)}ms
|
||||||
</span>
|
</span>
|
||||||
) : null}
|
) : null}
|
||||||
</h2>
|
</h2>
|
||||||
|
@ -26,7 +26,7 @@ const Service = ({ graph, name, status, latency, description }) => (
|
||||||
<p className="description">
|
<p className="description">
|
||||||
{description}
|
{description}
|
||||||
</p>
|
</p>
|
||||||
{graph ? <Graph width={500} height={175} data={graph} /> : null}
|
{graph ? <Graph data={graph} /> : null}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,7 @@ const ServicePlaceholder = () => (
|
||||||
<ReactPlaceholder
|
<ReactPlaceholder
|
||||||
type="rect"
|
type="rect"
|
||||||
ready={false}
|
ready={false}
|
||||||
style={{ width: '100%', height: '6rem', marginTop: '1rem' }}
|
style={{ width: '100%', height: '175px', marginTop: '1rem' }}
|
||||||
showLoadingAnimation
|
showLoadingAnimation
|
||||||
>
|
>
|
||||||
{' '}
|
{' '}
|
||||||
|
|
Loading…
Reference in a new issue