console/frontend: add a graph on the homepage

This commit is contained in:
Vincent Bernat
2022-04-21 16:07:01 +02:00
parent e8c3790379
commit 370cd6f961
6 changed files with 185 additions and 2 deletions

View File

@@ -0,0 +1,85 @@
<template>
<div>
<div class="h-[300px]">
<v-chart :option="option" autoresize />
</div>
</div>
</template>
<script setup>
import { ref, watch } from "vue";
import { use, graphic } from "echarts/core";
import { CanvasRenderer } from "echarts/renderers";
import { LineChart } from "echarts/charts";
import { TooltipComponent, GridComponent } from "echarts/components";
import VChart from "vue-echarts";
use([CanvasRenderer, LineChart, TooltipComponent, GridComponent]);
const formatGbps = (value) => {
const suffixes = ["", "K", "M", "G", "T"];
let idx = 0;
value *= 1000 * 1000 * 1000;
while (value >= 1000 && idx < suffixes.length) {
value /= 1000;
idx++;
}
value = value.toFixed(2);
return `${value}${suffixes[idx]}`;
};
const props = defineProps({
refresh: {
type: Number,
required: true,
},
});
const option = ref({
xAxis: { type: "time" },
yAxis: {
type: "value",
min: 0,
axisLabel: { formatter: formatGbps },
},
tooltip: {
trigger: "axis",
axisPointer: {
type: "cross",
label: { backgroundColor: "#6a7985" },
},
valueFormatter: formatGbps,
},
series: [
{
type: "line",
symbol: "none",
lineStyle: {
color: "#5470c6",
width: 1,
},
areaStyle: {
opacity: 1,
color: new graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: "#5470c6" },
{ offset: 1, color: "#5572c8" },
]),
},
data: [],
},
],
});
watch(
() => props.refresh,
async () => {
const response = await fetch("/api/v0/console/widget/graph?width=150");
if (!response.ok) {
// Keep current data
return;
}
const data = await response.json();
option.value.series[0].data = data.data.map(({ t, gbps }) => [t, gbps]);
},
{ immediate: true }
);
</script>

View File

@@ -60,7 +60,6 @@ const option = ref({
if (name === "Others") {
return "#aaa";
}
console.log(option.value);
return ["#5470c6", "#91cc75", "#fac858", "#ee6666", "#73c0de"][
dataIndex % 5
];

View File

@@ -39,6 +39,10 @@
title="Top countries"
:refresh="refreshOccasionally"
/>
<WidgetGraph
:refresh="refreshInfrequently"
class="col-span-2 md:col-span-4"
/>
</div>
</div>
</template>
@@ -49,13 +53,17 @@ import WidgetLastFlow from "../components/WidgetLastFlow.vue";
import WidgetFlowRate from "../components/WidgetFlowRate.vue";
import WidgetExporters from "../components/WidgetExporters.vue";
import WidgetTop from "../components/WidgetTop.vue";
import WidgetGraph from "../components/WidgetGraph.vue";
const refreshOften = ref(0);
const refreshOccasionally = ref(0);
const refreshInfrequently = ref(0);
let timerOften = setInterval(() => refreshOften.value++, 10_000);
let timerOccasionally = setInterval(() => refreshOccasionally.value++, 60_000);
let timerInfrequently = setInterval(() => refreshInfrequently.value++, 600_000);
onBeforeUnmount(() => {
clearInterval(timerOften);
clearInterval(timerOccasionally);
clearInterval(timerInfrequently);
});
</script>

View File

@@ -59,6 +59,7 @@ func (c *Component) Start() error {
c.d.HTTP.GinRouter.GET("/api/v0/console/widget/flow-rate", c.widgetFlowRateHandlerFunc)
c.d.HTTP.GinRouter.GET("/api/v0/console/widget/exporters", c.widgetExportersHandlerFunc)
c.d.HTTP.GinRouter.GET("/api/v0/console/widget/top/:name", c.widgetTopHandlerFunc)
c.d.HTTP.GinRouter.GET("/api/v0/console/widget/graph", c.widgetGraphHandlerFunc)
c.t.Go(func() error {
select {

View File

@@ -4,6 +4,8 @@ import (
"fmt"
"net/http"
"reflect"
"strconv"
"time"
"github.com/gin-gonic/gin"
)
@@ -156,3 +158,41 @@ LIMIT 5
}
gc.JSON(http.StatusOK, gin.H{"top": results})
}
func (c *Component) widgetGraphHandlerFunc(gc *gin.Context) {
ctx := c.t.Context(gc.Request.Context())
width, err := strconv.ParseUint(gc.DefaultQuery("width", "500"), 10, 16)
if err != nil {
c.r.Err(err).Msg("invalid width parameter")
gc.JSON(http.StatusBadRequest, gin.H{"message": "Invalid width value."})
return
}
if width < 5 || width > 1000 {
gc.JSON(http.StatusBadRequest, gin.H{"message": "Width should be > 5 and < 1000"})
return
}
interval := uint64((24 * time.Hour).Seconds()) / width
query := fmt.Sprintf(`
SELECT
toStartOfInterval(TimeReceived, INTERVAL %d second) AS Time,
SUM(Bytes*SamplingRate*8/%d)/1000/1000/1000 AS Gbps
FROM flows
WHERE TimeReceived > date_sub(hour, 24, now())
AND InIfBoundary = 'external'
GROUP BY Time
ORDER BY Time`, interval, interval)
results := []struct {
Time time.Time `json:"t"`
Gbps float64 `json:"gbps"`
}{}
err = c.d.ClickHouseDB.Conn.Select(ctx, &results, query)
if err != nil {
c.r.Err(err).Msg("unable to query database")
gc.JSON(http.StatusInternalServerError, gin.H{"message": "Unable to query database."})
return
}
gc.JSON(http.StatusOK, gin.H{"data": results})
}

View File

@@ -185,7 +185,6 @@ func TestWidgetExporters(t *testing.T) {
},
},
})
}
func TestWidgetTop(t *testing.T) {
@@ -261,3 +260,54 @@ func TestWidgetTop(t *testing.T) {
},
})
}
func TestWidgetGraph(t *testing.T) {
r := reporter.NewMock(t)
ch, mockConn := clickhousedb.NewMock(t, r)
h := http.NewMock(t, r)
c, err := New(r, Configuration{}, Dependencies{
Daemon: daemon.NewMock(t),
HTTP: h,
ClickHouseDB: ch,
})
if err != nil {
t.Fatalf("New() error:\n%+v", err)
}
helpers.StartStop(t, c)
base := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
expected := []struct {
Time time.Time `json:"t"`
Gbps float64 `json:"gbps"`
}{
{base, 25.3},
{base.Add(time.Minute), 27.8},
{base.Add(2 * time.Minute), 26.4},
{base.Add(3 * time.Minute), 29.2},
{base.Add(4 * time.Minute), 21.3},
{base.Add(5 * time.Minute), 24.7},
}
mockConn.EXPECT().
Select(gomock.Any(), gomock.Any(), `
SELECT
toStartOfInterval(TimeReceived, INTERVAL 864 second) AS Time,
SUM(Bytes*SamplingRate*8/864)/1000/1000/1000 AS Gbps
FROM flows
WHERE TimeReceived > date_sub(hour, 24, now())
AND InIfBoundary = 'external'
GROUP BY Time
ORDER BY Time`).
SetArg(1, expected).
Return(nil)
helpers.TestHTTPEndpoints(t, h.Address, helpers.HTTPEndpointCases{
{
URL: "/api/v0/console/widget/graph?width=100",
ContentType: "application/json; charset=utf-8",
FirstLines: []string{
`{"data":[{"t":"2009-11-10T23:00:00Z","gbps":25.3},{"t":"2009-11-10T23:01:00Z","gbps":27.8},{"t":"2009-11-10T23:02:00Z","gbps":26.4},{"t":"2009-11-10T23:03:00Z","gbps":29.2},{"t":"2009-11-10T23:04:00Z","gbps":21.3},{"t":"2009-11-10T23:05:00Z","gbps":24.7}]}`,
},
},
})
}