Add frontend
This commit is contained in:
parent
38df6afb69
commit
5aa93ab47a
12 changed files with 7594 additions and 1 deletions
12
.gitignore
vendored
12
.gitignore
vendored
|
@ -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.
|
# The directory Mix will write compiled artifacts to.
|
||||||
/_build/
|
/_build/
|
||||||
|
|
||||||
|
|
23
frontend/package.json
Normal file
23
frontend/package.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
15
frontend/public/index.html
Normal file
15
frontend/public/index.html
Normal 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
8
frontend/src/App.css
Normal 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
48
frontend/src/App.js
Normal 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
3
frontend/src/Graph.css
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
.graph {
|
||||||
|
margin-top: 1em;
|
||||||
|
}
|
94
frontend/src/Graph.js
Normal file
94
frontend/src/Graph.js
Normal 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
22
frontend/src/Service.css
Normal 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
21
frontend/src/Service.js
Normal 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
17
frontend/src/index.css
Normal 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
10
frontend/src/index.js
Normal 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
7320
frontend/yarn.lock
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue