fe: add routing + proto incident hist page

This commit is contained in:
slice 2018-07-19 14:49:43 -07:00
parent a0b6c8f01d
commit d66aa77aa7
No known key found for this signature in database
GPG Key ID: 1508C19D7436A26D
10 changed files with 125 additions and 15 deletions

View File

@ -34,6 +34,18 @@
"prop-types": "^15.5.10" "prop-types": "^15.5.10"
} }
}, },
"@reach/router": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@reach/router/-/router-1.1.1.tgz",
"integrity": "sha1-JKWyDxzJ5V4svNxFT7gslNtHmoE=",
"requires": {
"create-react-context": "^0.2.1",
"invariant": "^2.2.3",
"prop-types": "^15.6.1",
"react-lifecycles-compat": "^3.0.4",
"warning": "^3.0.0"
}
},
"abab": { "abab": {
"version": "1.0.4", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/abab/-/abab-1.0.4.tgz", "resolved": "https://registry.npmjs.org/abab/-/abab-1.0.4.tgz",
@ -2258,6 +2270,15 @@
"sha.js": "^2.4.8" "sha.js": "^2.4.8"
} }
}, },
"create-react-context": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/create-react-context/-/create-react-context-0.2.2.tgz",
"integrity": "sha512-KkpaLARMhsTsgp0d2NA/R94F/eDLbhXERdIq3LvX2biCAXcDvHYoOqHfWCHf1+OLj+HKBotLG3KqaOOf+C1C+A==",
"requires": {
"fbjs": "^0.8.0",
"gud": "^1.0.0"
}
},
"cross-spawn": { "cross-spawn": {
"version": "5.1.0", "version": "5.1.0",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz",
@ -4645,6 +4666,11 @@
"resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz",
"integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=" "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE="
}, },
"gud": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/gud/-/gud-1.0.0.tgz",
"integrity": "sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw=="
},
"gzip-size": { "gzip-size": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-3.0.0.tgz", "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-3.0.0.tgz",
@ -10838,6 +10864,14 @@
"makeerror": "1.0.x" "makeerror": "1.0.x"
} }
}, },
"warning": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz",
"integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=",
"requires": {
"loose-envify": "^1.0.0"
}
},
"watch": { "watch": {
"version": "0.10.0", "version": "0.10.0",
"resolved": "https://registry.npmjs.org/watch/-/watch-0.10.0.tgz", "resolved": "https://registry.npmjs.org/watch/-/watch-0.10.0.tgz",

View File

@ -7,6 +7,7 @@
"@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",
"@reach/router": "^1.1.1",
"classnames": "^2.2.6", "classnames": "^2.2.6",
"ms": "^2.1.1", "ms": "^2.1.1",
"nanoevents": "^1.0.5", "nanoevents": "^1.0.5",

View File

@ -0,0 +1,15 @@
import React from 'react'
import { Router } from '@reach/router'
import Dashboard from './Dashboard'
import Incidents from './Incidents'
export default function App () {
return (
<Router>
<Dashboard path="/"/>
<Incidents path="/incidents/"/>
</Router>
)
}

View File

@ -1,11 +1,11 @@
import React, { Component } from 'react' import React, { Component } from 'react'
import './Dashboard.css'
import Service from './Service.js' import Service from './Service.js'
import ServicePlaceholder from './ServicePlaceholder.js' import ServicePlaceholder from './ServicePlaceholder.js'
import DegradedNotice from './DegradedNotice.js' import DegradedNotice from './DegradedNotice.js'
import StreamingClient from '../ws/client' import StreamingClient from '../ws/client'
import Status from './Status' import Status from './Status'
import Page from './Page'
import { log, objectFromEntries, strictFetch } from '../util.js' import { log, objectFromEntries, strictFetch } from '../util.js'
import { domain as DOMAIN } from '../config.json' import { domain as DOMAIN } from '../config.json'
@ -164,10 +164,10 @@ export default class Dashboard extends Component {
render () { render () {
return ( return (
<div className="dashboard"> <Page>
<h1>elstatus</h1> <h1>elstatus</h1>
{this.renderDashboardContent()} {this.renderDashboardContent()}
</div> </Page>
) )
} }
} }

View File

@ -14,8 +14,13 @@ const OUTAGE_TYPES = {
export default function Incident ({ incident }) { export default function Incident ({ incident }) {
const { content, end_date: end, start_date: start, title, type, stages } = incident const { content, end_date: end, start_date: start, title, type, stages } = incident
const startDate = new Date(start * 1000)
const endDate = end ? new Date(end * 1000) : null
const tooltip = `Started ${startDate.toLocaleString()}` +
(endDate ? `, ended ${endDate.toLocaleString()}` : '')
const ago = ms(Date.now() - start * 1000) const ago = ms(Date.now() - start * 1000)
const agoEnd = end ? ms(Date.now - end * 1000) : null const agoEnd = end ? ms(Date.now() - end * 1000) : null
const stageNodes = stages.map( const stageNodes = stages.map(
(stage) => <Stage key={stage.title} stage={stage}/> (stage) => <Stage key={stage.title} stage={stage}/>
@ -25,7 +30,7 @@ export default function Incident ({ incident }) {
<div className="incident"> <div className="incident">
<h2>{OUTAGE_TYPES[type] || type}: {title}</h2> <h2>{OUTAGE_TYPES[type] || type}: {title}</h2>
<p>{content}</p> <p>{content}</p>
<footer> <footer title={tooltip}>
Started {ago} ago Started {ago} ago
{end ? `, ended ${agoEnd} ago` : null} {end ? `, ended ${agoEnd} ago` : null}
</footer> </footer>

View File

@ -0,0 +1,41 @@
import React from 'react'
import Page from './Page'
import Incident from './Incident'
import { strictFetch } from '../util'
import { domain as DOMAIN } from '../config.json'
export default class Incidents extends React.Component {
state = {
incidents: null,
page: 0,
}
componentDidMount () {
this.fetchIncidents()
}
async fetchIncidents () {
const resp = await strictFetch(`${DOMAIN}/api/incidents/${this.state.page}`)
this.setState({ incidents: await resp.json() })
}
renderIncidents () {
if (!this.state.incidents) {
return null
}
return this.state.incidents.map(
(incident) => <Incident key={incident.id} incident={incident}/>
)
}
render () {
return (
<Page>
<h1>Incidents</h1>
{this.renderIncidents()}
</Page>
)
}
}

View File

@ -1,4 +1,4 @@
.dashboard { .page {
max-width: 40em; max-width: 40em;
margin: 0 auto; margin: 0 auto;
} }

View File

@ -0,0 +1,16 @@
import React from 'react'
import PropTypes from 'prop-types'
import './Page.css'
export default function Page ({ children }) {
return (
<div className="page">
{children}
</div>
)
}
Page.propTypes = {
children: PropTypes.any,
}

View File

@ -6,16 +6,14 @@ import './Status.css'
import Incident from './Incident' import Incident from './Incident'
export default function Status ({ incident }) { export default function Status ({ incident }) {
let view = null const incidentOngoing = incident && incident.ongoing === 1
if (incident) { const view = incidentOngoing
view = <Incident incident={incident}/> ? <Incident incident={incident}/>
} else { : 'All systems operational'
view = 'All systems operational'
}
const className = classnames( const className = classnames(
'status', 'status',
incident == null ? 'status-good' : 'status-bad', incidentOngoing ? 'status-bad' : 'status-good',
) )
return ( return (
<div className={className}> <div className={className}>

View File

@ -2,12 +2,12 @@ import React from 'react'
import ReactDOM from 'react-dom' import ReactDOM from 'react-dom'
import './index.css' import './index.css'
import Dashboard from './components/Dashboard' import App from './components/App'
import register from './icons.js' import register from './icons.js'
register() register()
ReactDOM.render( ReactDOM.render(
<Dashboard/>, <App/>,
document.getElementById('root') document.getElementById('root')
) )