Add frontend
This commit is contained in:
parent
38df6afb69
commit
5aa93ab47a
|
@ -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/
|
||||
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -0,0 +1,8 @@
|
|||
.dashboard {
|
||||
max-width: 40em;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.services .service:not(:last-child) {
|
||||
margin-bottom: 1em;
|
||||
}
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
.graph {
|
||||
margin-top: 1em;
|
||||
}
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
|
@ -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;
|
||||
}
|
|
@ -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')
|
||||
);
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue