diff --git a/backend/database/database.go b/backend/database/database.go index 534fd36..ca64e26 100644 --- a/backend/database/database.go +++ b/backend/database/database.go @@ -13,9 +13,10 @@ type Database interface { AllNodes() ([]*model.Node, error) AllJobs() ([]*model.Job, error) - QueryTemperatureSamples(from, to time.Time) ([]model.Sample[float64], error) + QueryVoltageSamples(from, to time.Time) ([]model.Sample[float64], error) QueryMemorySamples(from, to time.Time) ([]model.Sample[int64], error) QueryStorageSamples(from, to time.Time) ([]model.Sample[int64], error) + QueryCPUSamples(from, to time.Time) ([]model.Sample[float64], error) QueryNetworkUploadSamples(from, to time.Time) ([]model.Sample[int64], error) QueryNetworkDownloadSamples(from, to time.Time) ([]model.Sample[int64], error) } diff --git a/backend/database/mem.go b/backend/database/mem.go index 1e36b38..be63f60 100644 --- a/backend/database/mem.go +++ b/backend/database/mem.go @@ -110,7 +110,7 @@ func (s *simpleDB) AllJobs() ([]*model.Job, error) { }, nil } -func (s *simpleDB) QueryTemperatureSamples(from, to time.Time) ([]model.Sample[float64], error) { +func (s *simpleDB) QueryVoltageSamples(from, to time.Time) ([]model.Sample[float64], error) { return generateFakeFloat64Samples(100), nil } @@ -122,6 +122,10 @@ func (s *simpleDB) QueryStorageSamples(from, to time.Time) ([]model.Sample[int64 return generateFakeInt64Samples(100), nil } +func (s *simpleDB) QueryCPUSamples(from, to time.Time) ([]model.Sample[float64], error) { + return generateFakeFloat64Samples(100), nil +} + func (s *simpleDB) QueryNetworkUploadSamples(from, to time.Time) ([]model.Sample[int64], error) { return generateFakeInt64Samples(100), nil } diff --git a/backend/routes/api.go b/backend/routes/api.go index 08f3d8b..fc0788a 100644 --- a/backend/routes/api.go +++ b/backend/routes/api.go @@ -62,9 +62,9 @@ func (r *Service) Api(api fiber.Router) { return c.JSON(jobs) }) - // QueryTemperatureSamples - api.Get("/stats/temperature", QueryTimeRangeMiddleware(func(c *fiber.Ctx, from, to time.Time) error { - samples, err := r.Database.QueryTemperatureSamples(from, to) + // QueryVoltageSamples + api.Get("/stats/voltage", QueryTimeRangeMiddleware(func(c *fiber.Ctx, from, to time.Time) error { + samples, err := r.Database.QueryVoltageSamples(from, to) if err != nil { return err } @@ -92,6 +92,16 @@ func (r *Service) Api(api fiber.Router) { return c.JSON(samples) })) + // QueryCPUSamples + api.Get("/stats/cpu", QueryTimeRangeMiddleware(func(c *fiber.Ctx, from, to time.Time) error { + samples, err := r.Database.QueryCPUSamples(from, to) + if err != nil { + return err + } + + return c.JSON(samples) + })) + // QueryNetworkUploadSamples api.Get("/stats/network-upload", QueryTimeRangeMiddleware(func(c *fiber.Ctx, from, to time.Time) error { samples, err := r.Database.QueryNetworkUploadSamples(from, to) diff --git a/frontend/src/components/GraphDouble.jsx b/frontend/src/components/GraphDouble.jsx new file mode 100644 index 0000000..236e600 --- /dev/null +++ b/frontend/src/components/GraphDouble.jsx @@ -0,0 +1,27 @@ +import { useEffect, useState } from 'preact/hooks' +import { server } from '../utils.js' +import { PlotDouble } from './PlotDouble.jsx' + +export const GraphCardDouble = ({ label, url1, url2 }) => { + const [samples1, setSamples1] = useState([]) + const [samples2, setSamples2] = useState([]) + + useEffect(() => { + if (url1) { + server.get(url1).then(data => setSamples1(data)) + } + }, []) + + useEffect(() => { + if (url2) { + server.get(url2).then(data => setSamples2(data)) + } + }, []) + + return ( +
+
{label}
+
{url1 && url2 && }
+
+ ) +} diff --git a/frontend/src/components/PlotDouble.jsx b/frontend/src/components/PlotDouble.jsx new file mode 100644 index 0000000..6087589 --- /dev/null +++ b/frontend/src/components/PlotDouble.jsx @@ -0,0 +1,102 @@ +import * as d3 from 'd3' +import { useEffect, useRef } from 'preact/hooks' + +// set the dimensions and margins of the graph +const margin = { top: 10, right: 10, bottom: 30, left: 30 } + +export const PlotDouble = ({ samples1, samples2 }) => { + if (samples1.length === 0 || samples2.length === 0) { + return <>Loading... + } + + const data1 = samples1.map(({ timestamp, value }) => ({ + timestamp: d3.isoParse(timestamp), + value: value, + })) + + const data2 = samples2.map(({ timestamp, value }) => ({ + timestamp: d3.isoParse(timestamp), + value: value, + })) + + const plotRef = useRef(null) + + useEffect(() => { + if (plotRef.current) { + const el = plotRef.current + + const width = el.offsetWidth - margin.left - margin.right + const height = el.offsetHeight - margin.top - margin.bottom + + const svg = d3 + .select(el) + .append('svg') + .attr('width', el.offsetWidth) + .attr('height', el.offsetHeight) + .append('g') + .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')') + + const x = d3 + .scaleTime() + .domain( + d3.extent([...data1, ...data2], function (d) { + return d.timestamp + }) + ) + .range([0, width]) + + const y = d3 + .scaleLinear() + .domain([ + 0, + d3.max([...data1, ...data2], function (d) { + return +d.value + }), + ]) + .range([height, 0]) + + svg.append('g') + .attr('transform', 'translate(0,' + height + ')') + .call(d3.axisBottom(x)) + + svg.append('g').call(d3.axisLeft(y)) + + const line1 = svg.append('g') + const line2 = svg.append('g') + + line1.append('path') + .datum(data1) + .attr('fill', 'none') + .attr('stroke', 'steelblue') + .attr('stroke-width', 2) + .attr( + 'd', + d3 + .line() + .x(d => x(d.timestamp)) + .y(d => y(d.value)) + ) + + line2.append('path') + .datum(data2) + .attr('fill', 'none') + .attr('stroke', 'darkgreen') + .attr('stroke-width', 2) + .attr( + 'd', + d3 + .line() + .x(d => x(d.timestamp)) + .y(d => y(d.value)) + ) + } + + return () => { + if (plotRef.current) { + plotRef.current.firstChild.remove() + } + } + }, [plotRef]) + + return
+} diff --git a/frontend/src/pages.jsx b/frontend/src/pages.jsx index 8e02014..3c98eff 100644 --- a/frontend/src/pages.jsx +++ b/frontend/src/pages.jsx @@ -1,4 +1,5 @@ import { GraphCard } from './components/Graph.jsx' +import { GraphCardDouble } from './components/GraphDouble.jsx' const LOGO = String.raw` _______________________ _______ _______ _______ _ @@ -85,11 +86,11 @@ export const DashboardPage = ({}) => {
- - - - - + + + + +
)