Add frontend

This commit is contained in:
slice 2018-06-12 14:56:50 -07:00
parent 38df6afb69
commit 5aa93ab47a
No known key found for this signature in database
GPG Key ID: 1508C19D7436A26D
12 changed files with 7594 additions and 1 deletions

14
.gitignore vendored
View File

@ -1,3 +1,15 @@
# OS stuff
.DS_Store
thumbs.db
# Frontend stuff
/frontend/node_modules
/frontend/build
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# The directory Mix will write compiled artifacts to.
/_build/
@ -23,4 +35,4 @@ erl_crash.dump
elstat-*.tar
*.db
config/config.exs
config/config.exs

23
frontend/package.json Normal file
View File

@ -0,0 +1,23 @@
{
"name": "elstatus",
"version": "0.1.0",
"private": true,
"dependencies": {
"@vx/axis": "^0.0.165",
"@vx/curve": "^0.0.165",
"@vx/gradient": "^0.0.165",
"@vx/group": "^0.0.165",
"@vx/scale": "^0.0.165",
"@vx/shape": "^0.0.165",
"d3-array": "^1.2.1",
"react": "^16.4.0",
"react-dom": "^16.4.0",
"react-scripts": "1.1.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
}
}

View File

@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<title>elstatus</title>
</head>
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>
</body>
</html>

8
frontend/src/App.css Normal file
View File

@ -0,0 +1,8 @@
.dashboard {
max-width: 40em;
margin: 0 auto;
}
.services .service:not(:last-child) {
margin-bottom: 1em;
}

48
frontend/src/App.js Normal file
View File

@ -0,0 +1,48 @@
import React, { Component } from 'react';
import Service from './Service.js';
import './App.css';
const ENDPOINT = 'https://elstatus.stayathomeserver.club/api/status'
export default class App extends Component {
state = {
loading: true,
error: null,
metrics: null,
};
componentDidMount() {
fetch(ENDPOINT)
.then((resp) => resp.json())
.then((data) => {
this.setState({ metrics: data, loading: false });
})
.catch((error) => {
this.setState({ error: error.toString() });
})
}
render() {
const metrics = !this.state.metrics ? null : (
<div className="services">
{Object.entries(this.state.metrics.status)
.map(([name, info]) =>
<Service name={name} key={name}
graph={this.state.metrics.graph[name]}
{...info}
/>)
}
</div>
);
return (
<div className="dashboard">
<h1>elstatus</h1>
{this.state.loading ? <div>Loading metrics...</div> : null}
{this.state.error ? <div>Error: {this.state.error}</div> : null}
{metrics}
</div>
);
}
}

3
frontend/src/Graph.css Normal file
View File

@ -0,0 +1,3 @@
.graph {
margin-top: 1em;
}

94
frontend/src/Graph.js Normal file
View File

@ -0,0 +1,94 @@
import React, { Component } from 'react';
import './Graph.css';
import { curveNatural } from '@vx/curve';
import { GradientOrangeRed } from '@vx/gradient';
import { AxisLeft, AxisBottom } from '@vx/axis';
import { Group } from '@vx/group';
import { AreaClosed } from '@vx/shape';
import { scaleTime, scaleLinear } from '@vx/scale';
import { extent, max } from 'd3-array';
const x = ([timestamp,]) => new Date(timestamp);
const y = ([, latency]) => latency;
const margin = {
top: 0,
bottom: 50,
left: 50,
right: 0,
};
const tick = {
textAnchor: 'middle',
fontSize: 8,
fontFamily: 'system-ui, sans-serif',
};
const leftTick = {
...tick,
dx: '-0.25em',
dy: '0.25em',
textAnchor: 'end',
};
const label = {
...tick,
fontSize: 10,
};
export default class Graph extends Component {
constructor(props) {
super(props);
const { width, height, data } = props;
this.xMax = width - margin.left - margin.right;
this.yMax = height - margin.top - margin.bottom;
this.xScale = scaleTime({
range: [0, this.xMax],
domain: extent(data, x),
});
this.yScale = scaleLinear({
range: [this.yMax, 0],
domain: [0, max(data, y)],
nice: true,
});
}
render() {
return (
<svg className="graph" width={this.props.width} height={this.props.height}>
<GradientOrangeRed id="gradient"/>
<Group top={margin.top} left={margin.left}>
<AreaClosed
data={this.props.data}
xScale={this.xScale} yScale={this.yScale}
x={x} y={y}
stroke=""
fill="url(#gradient)"
curve={curveNatural}
/>
<AxisLeft
scale={this.yScale}
top={0} left={0}
labelProps={label}
tickFormat={(value, index) => `${value}ms`}
tickLabelProps={(value, index) => leftTick}
label=''
/>
<AxisBottom
scale={this.xScale}
top={this.yMax}
tickFormat={(value, index) => `${value.toLocaleTimeString()}`}
labelProps={label}
tickLabelProps={(value, index) => tick}
label=''
/>
</Group>
</svg>
);
}
}

22
frontend/src/Service.css Normal file
View File

@ -0,0 +1,22 @@
.service__header {
display: flex;
align-items: center;
}
.service__title__latency {
margin-left: 0.5em;
opacity: 0.3;
}
.service__header h2 {
margin: 0;
}
.service__header__emoji {
font-size: 2em;
margin-right: 0.5em;
}
.service__description {
margin: 1em 0 0 0;
}

21
frontend/src/Service.js Normal file
View File

@ -0,0 +1,21 @@
import React from 'react';
import Graph from './Graph.js';
import './Service.css';
const Service = ({ graph, name, status, latency, description }) =>
<div className="service">
<header className="service__header">
<div className="service__header__emoji">{status ? '✅' : '🚫'}</div>
<h2 className="service__title">
{name}
{latency ? <span className="service__title__latency">{latency}ms</span> : null}
</h2>
</header>
<p className="service__description">
{description}
</p>
{graph ? <Graph width={500} height={175} data={graph}/> : null}
</div>
export default Service;

17
frontend/src/index.css Normal file
View File

@ -0,0 +1,17 @@
html, body {
box-sizing: border-box;
}
body {
margin: 0;
padding: 1rem;
font: 16px/1.5 system-ui, sans-serif;
}
h1:first-child {
margin-top: 0;
}
*, *:before, *:after {
box-sizing: inherit;
}

10
frontend/src/index.js Normal file
View File

@ -0,0 +1,10 @@
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
ReactDOM.render(
<App />,
document.getElementById('root')
);

7320
frontend/yarn.lock Normal file

File diff suppressed because it is too large Load Diff