import React, { Component } from 'react' import Service from './Service.js' import ServicePlaceholder from './ServicePlaceholder.js' import DegradedNotice from './DegradedNotice.js' import StreamingClient from '../ws/client' import Status from './Status' import Page from './Page' import { log, objectFromEntries, strictFetch } from '../util.js' import { domain as DOMAIN } from '../config.json' export default class Dashboard extends Component { client = null state = { loading: true, error: null, metrics: null, incident: null, } async componentDidMount() { try { await this.loadMetrics() await this.loadIncident() } catch (error) { this.setState({ error: error.toString() }) } this.setState({ loading: false }) if (this.state.error) { return } const endpoint = `${DOMAIN}/api/streaming` .replace('https', 'wss') .replace('http', 'ws') this.client = new StreamingClient(endpoint, this.state.metrics) this.client.connect() this.client.on('status', this.handleStatus.bind(this)) this.client.on('latency', this.handleLatency.bind(this)) this.client.on('incident_new', (incident) => { this.setState({ incident }) }) this.client.on('incident_update', (incident) => { this.setState({ incident }) }) this.client.on('incident_close', () => { this.setState({ incident: null }) }) } handleStatus(name, [, status]) { const { status: statuses } = this.state.metrics log('updating status on:', name) if (!(name in statuses)) { log(`failed to locate channel ${name} to update statuses`) return } if (statuses[name].status === status) { log(`ignoring stale status (${status}) for ${name}`) return } this.setState(({ metrics: old }, _props) => { const metrics = { ...old } metrics.status[name].status = status return { metrics } }) } handleLatency(name, data) { const { metrics } = this.state log('adding latency entry:', data) // latency entries come in newest to oldest, so remove the oldest entry const graph = metrics.graph[name].slice(0, metrics.graph[name].length - 1) // make new data come in first const newGraph = [data, ...graph] this.setState(({ metrics: old }, _props) => { const metrics = { ...old } metrics.graph[name] = newGraph const [, latency] = data metrics.status[name].latency = latency return { metrics } }) } async loadMetrics() { log('loading metrics') const resp = await strictFetch(`${DOMAIN}/api/status`) this.setState({ metrics: await resp.json() }) } async loadIncident() { log('loading current incident') const resp = await strictFetch(`${DOMAIN}/api/incidents/current`) this.setState({ incident: await resp.json() }) } renderNotice(services) { const down = services.filter(([, { status }]) => !status) // DegradedNotice should only be shown when there is no ongoing incident, // and any services are reported as down. if (!this.state.incident && down.length > 0) { return } return } renderServices(services) { const { graph: graphs, uptime: uptimes } = this.state.metrics return services.map(([name, info]) => ( )) } renderPlaceholders() { return ( ) } renderDashboardContent() { if (this.state.error) { return
{this.state.error}
} if (this.state.loading) { return this.renderPlaceholders() } const allServices = Object.entries(this.state.metrics.status) const services = this.renderServices(allServices) const notice = this.renderNotice(allServices) return ( {notice} {services} ) } render() { return (

elstatus

{this.renderDashboardContent()}
) } }