From 6d3b35ee66ddde5817fa99d4dd82c522c46e59b0 Mon Sep 17 00:00:00 2001 From: Antonio De Lucreziis Date: Sat, 13 May 2023 00:08:36 +0200 Subject: [PATCH] feat: refactor di alcune cose e grafici --- backend/database/mem.go | 104 ++++++++++++++++++++++++++--- frontend/index.html | 107 +----------------------------- frontend/src/components/Graph.jsx | 20 ++++++ frontend/src/components/Plot.jsx | 88 +++++++++++++++++++++--- frontend/src/main.jsx | 1 + frontend/src/pages.jsx | 96 ++++++++++++++++++++++++++- frontend/src/utils.js | 19 ++++++ frontend/styles/main.scss | 28 ++++++-- 8 files changed, 333 insertions(+), 130 deletions(-) create mode 100644 frontend/src/components/Graph.jsx create mode 100644 frontend/src/utils.js diff --git a/backend/database/mem.go b/backend/database/mem.go index 88a4447..1e36b38 100644 --- a/backend/database/mem.go +++ b/backend/database/mem.go @@ -1,11 +1,11 @@ package database import ( + "math/rand" "time" "git.phc.dm.unipi.it/phc/cluster-dashboard/backend/executor" "git.phc.dm.unipi.it/phc/cluster-dashboard/backend/model" - "golang.org/x/exp/maps" ) // simpleDB รจ una implementazione di [database.Database] che tiene giusto una cache in memoria e @@ -37,37 +37,121 @@ func NewSimpleDatabase(ex executor.Service) Database { } func (s *simpleDB) GetNode(hostname string) (*model.Node, error) { - panic("todo") + return &model.Node{ + Hostname: hostname, + StartTime: time.Now().Add(-1 * time.Hour), + }, nil } func (s *simpleDB) GetJob(id string) (*model.Job, error) { - panic("todo") + return &model.Job{ + Id: id, + Name: "example-job", + Status: "active", + Nodes: []string{"node-1", "node-2"}, + Resources: []string{}, + }, nil } func (s *simpleDB) AllNodes() ([]*model.Node, error) { - return maps.Values(s.nodes), nil + return []*model.Node{ + { + Hostname: "node-1", + StartTime: time.Now().Add(-1 * time.Hour), + }, + { + Hostname: "node-2", + StartTime: time.Now().Add(-1 * time.Hour), + }, + { + Hostname: "node-3", + StartTime: time.Now().Add(-1 * time.Hour), + }, + }, nil } func (s *simpleDB) AllJobs() ([]*model.Job, error) { - return maps.Values(s.jobs), nil + return []*model.Job{ + { + Id: "job-1", + Name: "example-job", + Status: "active", + Nodes: []string{"node-1", "node-2"}, + Resources: []string{}, + }, + { + Id: "job-2", + Name: "example-job", + Status: "active", + Nodes: []string{"node-1", "node-2"}, + Resources: []string{}, + }, + { + Id: "job-3", + Name: "example-job", + Status: "active", + Nodes: []string{"node-1", "node-2"}, + Resources: []string{}, + }, + { + Id: "job-4", + Name: "example-job", + Status: "active", + Nodes: []string{"node-1", "node-2"}, + Resources: []string{}, + }, + { + Id: "job-5", + Name: "example-job", + Status: "active", + Nodes: []string{"node-1", "node-2"}, + Resources: []string{}, + }, + }, nil } func (s *simpleDB) QueryTemperatureSamples(from, to time.Time) ([]model.Sample[float64], error) { - panic("todo") + return generateFakeFloat64Samples(100), nil } func (s *simpleDB) QueryMemorySamples(from, to time.Time) ([]model.Sample[int64], error) { - panic("todo") + return generateFakeInt64Samples(100), nil } func (s *simpleDB) QueryStorageSamples(from, to time.Time) ([]model.Sample[int64], error) { - panic("todo") + return generateFakeInt64Samples(100), nil } func (s *simpleDB) QueryNetworkUploadSamples(from, to time.Time) ([]model.Sample[int64], error) { - panic("todo") + return generateFakeInt64Samples(100), nil } func (s *simpleDB) QueryNetworkDownloadSamples(from, to time.Time) ([]model.Sample[int64], error) { - panic("todo") + return generateFakeInt64Samples(100), nil +} + +func generateFakeFloat64Samples(amount int) []model.Sample[float64] { + fakeSamples := make([]model.Sample[float64], amount) + + for i := range fakeSamples { + fakeSamples[i] = model.Sample[float64]{ + Timestamp: time.Now().Add(time.Duration(i-amount) * time.Second), + Value: rand.Float64(), + } + } + + return fakeSamples +} + +func generateFakeInt64Samples(amount int) []model.Sample[int64] { + fakeSamples := make([]model.Sample[int64], amount) + + for i := range fakeSamples { + fakeSamples[i] = model.Sample[int64]{ + Timestamp: time.Now().Add(time.Duration(i-amount) * time.Second), + Value: rand.Int63n(1000), + } + } + + return fakeSamples } diff --git a/frontend/index.html b/frontend/index.html index 0df5b7e..fbac936 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -25,111 +25,6 @@ - -
-
-
Uptime
-
-
-
-
CPU
-
-
-
-
Storage
-
-
-
-
Memory
-
-
-
-
Network
-
-
-
-
Temperature
-
-
-
+ diff --git a/frontend/src/components/Graph.jsx b/frontend/src/components/Graph.jsx new file mode 100644 index 0000000..d07fc59 --- /dev/null +++ b/frontend/src/components/Graph.jsx @@ -0,0 +1,20 @@ +import { useEffect, useState } from 'preact/hooks' +import { Plot } from './Plot.jsx' +import { server } from '../utils.js' + +export const GraphCard = ({ label, url }) => { + const [samples, setSamples] = useState([]) + + useEffect(() => { + if (url) { + server.get(url).then(data => setSamples(data)) + } + }, []) + + return ( +
+
{label}
+
{url && }
+
+ ) +} diff --git a/frontend/src/components/Plot.jsx b/frontend/src/components/Plot.jsx index 9b5675b..cc93a3c 100644 --- a/frontend/src/components/Plot.jsx +++ b/frontend/src/components/Plot.jsx @@ -1,13 +1,83 @@ -import d3 from 'd3' +import * as d3 from 'd3' +import { useEffect, useRef } from 'preact/hooks' -// export const Plot = ({ samples }) => { -// const mountElement = el => { -// d3.select(el).append('svg') -// } - -// return
-// } +// set the dimensions and margins of the graph +const margin = { top: 10, right: 10, bottom: 30, left: 30 } export const Plot = ({ samples }) => { - return
Work in progress...
+ if (samples.length === 0) { + return <>Loading... + } + + const data = samples.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(data, function (d) { + return d.timestamp + }) + ) + .range([0, width]) + + const y = d3 + .scaleLinear() + .domain([ + 0, + d3.max(data, 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 line = svg.append('g') + + line.append('path') + .datum(data) + .attr('fill', 'none') + .attr('stroke', 'steelblue') + .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/main.jsx b/frontend/src/main.jsx index 18f9459..04847ea 100644 --- a/frontend/src/main.jsx +++ b/frontend/src/main.jsx @@ -7,6 +7,7 @@ import { DashboardPage, JobsPage } from './pages.jsx' const Redirect = ({ to }) => { useEffect(() => { + console.log('Redirecting...') route(to, true) }, []) diff --git a/frontend/src/pages.jsx b/frontend/src/pages.jsx index e007656..8e02014 100644 --- a/frontend/src/pages.jsx +++ b/frontend/src/pages.jsx @@ -1,6 +1,100 @@ +import { GraphCard } from './components/Graph.jsx' + +const LOGO = String.raw` +_______________________ _______ _______ _______ _ +( ____ \__ __( ____ ( ____ ( ____ ( ____ ( ) +| ( \/ ) ( | ( \/ ( \/ ( \/ ( \// +| (_____ | | | (__ | (__ | (__ | (__ +(_____ ) | | | __) | __) | __) | __) + ) | | | | ( | ( | ( | ( +/\____) | | | | (____/\ ) | ) | (____/\ +\_______) )_( (_______// |/ (_______/ +`.trim() + export const DashboardPage = ({}) => { - return <>Dashboard Page + return ( + <> + +
+ + + + + + +
+ + ) } + export const JobsPage = ({}) => { return <>Jobs Page } diff --git a/frontend/src/utils.js b/frontend/src/utils.js new file mode 100644 index 0000000..1a484df --- /dev/null +++ b/frontend/src/utils.js @@ -0,0 +1,19 @@ +export const server = { + async get(url) { + const res = await fetch(url) + return await res.json() + }, + async post(url, data) { + const res = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(data), + }) + + return await res.json() + }, +} + +window.server = server diff --git a/frontend/styles/main.scss b/frontend/styles/main.scss index 449f8fa..45a7472 100644 --- a/frontend/styles/main.scss +++ b/frontend/styles/main.scss @@ -10,7 +10,7 @@ body { width: 100%; min-height: 100vh; - font-family: "Open Sans", "Segoe UI", "Helvetica", "Arial", sans-serif; + font-family: 'Open Sans', 'Segoe UI', 'Helvetica', 'Arial', sans-serif; font-size: 16px; color: #284a36; @@ -57,7 +57,7 @@ body { padding: 16px; - font-family: "Ubuntu Mono", monospace; + font-family: 'Ubuntu Mono', monospace; font-weight: 700; display: grid; @@ -99,6 +99,22 @@ body { &:hover { background-color: #e8ede9; } + + &.link { + padding: 0 16px; + + .label { + display: flex; + flex-grow: 1; + + a { + display: flex; + flex-grow: 1; + + padding: 8px 0; + } + } + } } } } @@ -128,9 +144,13 @@ body { } .graph { - background-color: #f4f5f9; - min-height: 300px; + + .d3-plot { + display: flex; + width: 100%; + height: 100%; + } } }