Skip to content

Commit

Permalink
Add first chart
Browse files Browse the repository at this point in the history
  • Loading branch information
kozaktomas committed Oct 31, 2024
1 parent 9880462 commit 38aa55d
Show file tree
Hide file tree
Showing 9 changed files with 388 additions and 9 deletions.
8 changes: 8 additions & 0 deletions backend/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ type Config struct {
Password string // shared admin password

FrontendPath string

PrometheusUrl string
PrometheusUser string
PrometheusPassword string
}

func NewConfig() *Config {
Expand All @@ -25,6 +29,10 @@ func NewConfig() *Config {
Password: getStringEnvDefault("PASSWORD", "test"),

FrontendPath: getStringEnvDefault("FRONTEND_PATH", "./../frontend/build/"),

PrometheusUrl: getStringEnvDefault("PROMETHEUS_URL", "http://localhost:9090"),
PrometheusUser: getStringEnvDefault("PROMETHEUS_USER", "test"),
PrometheusPassword: getStringEnvDefault("PROMETHEUS_PASSWORD", "test"),
}
}

Expand Down
13 changes: 9 additions & 4 deletions backend/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@ import (
)

type HandlerRepository struct {
scale *Scale
config *Config
monitor *Monitor
logger *logrus.Logger
scale *Scale
promector *Promector
config *Config
monitor *Monitor
logger *logrus.Logger
}

func (hr *HandlerRepository) scaleStatusHandler() func(http.ResponseWriter, *http.Request) {
Expand Down Expand Up @@ -171,6 +172,8 @@ func (hr *HandlerRepository) scaleDashboardHandler() func(http.ResponseWriter, *
IsLow bool `json:"is_low"`
Warehouse []warehouseItem `json:"warehouse"`
WarehouseBeerLeft int `json:"warehouse_beer_left"`

Charts Charts `json:"charts"`
}

units, err := durafmt.DefaultUnitsCoder.Decode(localizationUnits)
Expand Down Expand Up @@ -206,6 +209,8 @@ func (hr *HandlerRepository) scaleDashboardHandler() func(http.ResponseWriter, *
IsLow: hr.scale.IsLow,
Warehouse: warehouse,
WarehouseBeerLeft: GetWarehouseBeersLeft(hr.scale.Warehouse),

Charts: hr.promector.GetChartData(),
}

res, err := json.Marshal(data)
Expand Down
17 changes: 13 additions & 4 deletions backend/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,23 @@ func main() {
logger := createLogger()
monitor := NewMonitor()

promector := NewPromector(
config.PrometheusUrl,
config.PrometheusUser,
config.PrometheusPassword,
logger,
ctx,
)

store := NewRedisStore(config)

scale := NewScale(monitor, store, logger, ctx)
StartServer(NewRouter(&HandlerRepository{
scale: scale,
config: config,
monitor: monitor,
logger: logger,
scale: scale,
promector: promector,
config: config,
monitor: monitor,
logger: logger,
}), 8080, cancel)
}

Expand Down
211 changes: 211 additions & 0 deletions backend/promector.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
package main

import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"github.com/sirupsen/logrus"
"io"
"net/http"
"strconv"
"sync"
"time"
)

// Promector represents a Prometheus collector
type Promector struct {
baseUrl string
user string
password string

logger *logrus.Logger
ctx context.Context
mtx sync.RWMutex

data map[string][]RangeRecord
}

type PrometheusResponse struct {
Status string `json:"status"`
Data struct {
ResultType string `json:"resultType"`
Result []struct {
Values []interface{} `json:"values"`
} `json:"result"`
} `json:"data"`
}

func NewPromector(baseUrl, user, password string, logger *logrus.Logger, ctx context.Context) *Promector {
prom := &Promector{
baseUrl: baseUrl,
user: user,
password: password,

logger: logger,
ctx: ctx,
mtx: sync.RWMutex{},

data: make(map[string][]RangeRecord, 4),
}

// periodically call recheck
go func(prom *Promector) {
tick := time.NewTicker(2 * time.Minute)
defer tick.Stop()
for {
select {
case <-prom.ctx.Done():
prom.logger.Debug("Promector downloading stopped")
return
case <-tick.C:
prom.Refresh()
prom.logger.Debug("Promector data refreshed")
}
}
}(prom)
prom.Refresh()

return prom
}

type RangeRecord struct {
Label string `json:"label"`
Value int `json:"value"`
}

type ChartInterval struct {
Interval string `json:"interval"`
Values []RangeRecord `json:"values"`
}

type Charts struct {
BeersLeft []ChartInterval `json:"beers_left"`
}

func (p *Promector) Refresh() {
type request struct {
key string
query string
hours int
step string
}

requests := []request{
{"scale_beers_left_1h", "scale_beers_left", 1, "5m"},
{"scale_beers_left_3h", "scale_beers_left", 3, "10m"},
{"scale_beers_left_6h", "scale_beers_left", 6, "20m"},
{"scale_beers_left_24h", "scale_beers_left", 24, "1h"},
}

tmp := make(map[string][]RangeRecord, 4)

for _, r := range requests {
data, err := p.GetRangeData(r.query, r.hours, r.step)
if err != nil {
p.logger.Errorf("could not get range data for %s: %v", r.key, err)
continue
}

tmp[r.key] = data
}

p.mtx.Lock()
defer p.mtx.Unlock()
p.data = tmp
}

func (p *Promector) GetChartData() Charts {
p.mtx.RLock()
defer p.mtx.RUnlock()

return Charts{
BeersLeft: []ChartInterval{
{"1h", p.data["scale_beers_left_1h"]},
{"3h", p.data["scale_beers_left_3h"]},
{"6h", p.data["scale_beers_left_6h"]},
{"24h", p.data["scale_beers_left_24h"]},
},
}

}

func (p *Promector) GetRangeData(query string, hours int, step string) ([]RangeRecord, error) {
url := fmt.Sprintf("%s/api/v1/query_range?", p.baseUrl)
req, _ := http.NewRequest("GET", url, nil)

q := req.URL.Query()
q.Add("query", query)
q.Add("step", step)
q.Add("start", fmt.Sprintf("%d", time.Now().Unix()-(60*60*int64(hours))))
q.Add("end", fmt.Sprintf("%d", time.Now().Unix()))
req.URL.RawQuery = q.Encode()

req.Header.Add("Authorization", getBaseAuth(p.user, p.password))
client := &http.Client{
Timeout: 10 * time.Second, // Set the timeout duration here
}

response, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("could not get value from prometheus: %w", err)
}

if response.StatusCode != http.StatusOK {
return nil, fmt.Errorf("unexpected status code: %d", response.StatusCode)
}

data, err := io.ReadAll(response.Body)
if err != nil {
return nil, fmt.Errorf("could not read response body: %w", err)
}

var prometheusResponse PrometheusResponse
err = json.Unmarshal(data, &prometheusResponse)
if err != nil {
return nil, fmt.Errorf("could not unmarshal response body: %w", err)
}

if prometheusResponse.Status != "success" {
return nil, fmt.Errorf("prometheus query failed: %s", prometheusResponse.Status)
}

if len(prometheusResponse.Data.Result) != 1 {
return nil, fmt.Errorf("unexpected number of results: %d", len(prometheusResponse.Data.Result))
}

records := make([]RangeRecord, len(prometheusResponse.Data.Result[0].Values))

i := 0
for _, value := range prometheusResponse.Data.Result[0].Values {
record, ok := value.([]interface{})
if !ok {
return nil, fmt.Errorf("unexpected value type: %T", value)
}

if len(record) != 2 {
return nil, fmt.Errorf("unexpected number of values: %d", len(record))
}

t := time.Unix(int64(record[0].(float64)), 0)
v, e := strconv.Atoi(record[1].(string))
if e != nil {
return nil, fmt.Errorf("could not convert value to int: %w", e)
}

records[i] = RangeRecord{
Label: formatTime(t),
Value: v,
}

i++

}

return records, nil
}

func getBaseAuth(username, password string) string {
auth := username + ":" + password
return "Basic " + base64.StdEncoding.EncodeToString([]byte(auth))
}
2 changes: 2 additions & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@
"@testing-library/react": "^16.0.1",
"@testing-library/user-event": "^14.5.2",
"bootstrap": "^5.3.3",
"chart.js": "^4.4.6",
"install": "^0.13.0",
"react": "^18.3.1",
"react-bootstrap": "^2.10.4",
"react-chartjs-2": "^5.2.0",
"react-dom": "^18.3.1",
"react-router-dom": "^6.26.1",
"react-scripts": "5.0.1",
Expand Down
8 changes: 8 additions & 0 deletions frontend/src/Dashboard.css
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,12 @@
.cell-red {
background-color: #630e10;
color: #FFF;
}

.interval {
cursor: pointer;
}

.activeInterval {
font-weight: bold;
}
20 changes: 19 additions & 1 deletion frontend/src/Dashboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,20 @@ import Keg from "./Keg";
import {buildUrl} from "./Api";
import Pivo from "./Pivo";
import Field from "./Field";
import FieldChart from "./FieldChart";

function Dashboard() {

const defaultEmptyChart = [{
interval: "1h",
values: [
{
label: "0",
value: 0
}
]
}];

const defaultScale = {
is_ok: false,
beers_left: 0,
Expand Down Expand Up @@ -50,8 +61,13 @@ function Dashboard() {
}
],
warehouse_beer_left: 0,

charts: {
beers_left: defaultEmptyChart,
}
}


const [scale, setScale] = useState(defaultScale);
const [showKeg, setShowKeg] = useState(false);
const [showWarehouse, setShowWarehouse] = useState(false);
Expand Down Expand Up @@ -176,8 +192,10 @@ function Dashboard() {
>
{scale.rssi}&nbsp;db
</Field>

</Row>

<FieldChart title={"Zbýva piva"} chart={scale.charts.beers_left} loading={showSpinner}/>

</Container>
)
}
Expand Down
Loading

0 comments on commit 38aa55d

Please sign in to comment.