Compare commits
7 Commits
c267c4d2c4
...
98c81cb347
Author | SHA1 | Date | |
---|---|---|---|
98c81cb347 | |||
ce7ac50d6f | |||
28c9a6920e | |||
78e56f7e22 | |||
8a9cd908b7 | |||
fb25351742 | |||
01c0558dd2 |
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,8 +1,10 @@
|
||||
# generated
|
||||
assets/
|
||||
tmp/
|
||||
speedtester
|
||||
speed-tester
|
||||
*.db
|
||||
view/**/*_templ.go
|
||||
view/**/*_templ.txt
|
||||
|
||||
# dependencies
|
||||
node_modules/
|
||||
|
@ -6,7 +6,7 @@ import (
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"gitea.zokki.net/zokki/speedtester/models"
|
||||
"gitea.zokki.net/zokki/speed-tester/models"
|
||||
_ "github.com/mattn/go-sqlite3" // Import the SQLite driver
|
||||
"github.com/showwin/speedtest-go/speedtest"
|
||||
)
|
||||
@ -120,7 +120,8 @@ func InsertServer(server *speedtest.Server) {
|
||||
CheckError("insert into speedData", err)
|
||||
}
|
||||
|
||||
func GetAllData(ctx context.Context) ([]models.InternetData, error) {
|
||||
func GetAllData(ctx context.Context, from string, to string) ([]models.InternetData, error) {
|
||||
whereFilter, whereValues := whereStatement("CreationDate", from, to)
|
||||
rows, err := DB.QueryContext(ctx, `SELECT
|
||||
ID,
|
||||
CreationDate,
|
||||
@ -140,7 +141,7 @@ func GetAllData(ctx context.Context) ([]models.InternetData, error) {
|
||||
ULSpeed,
|
||||
TestDuration,
|
||||
PacketLoss
|
||||
FROM internetData;`)
|
||||
FROM internetData `+whereFilter, whereValues...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -161,6 +162,91 @@ func GetAllData(ctx context.Context) ([]models.InternetData, error) {
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func GetSpeedData(ctx context.Context, from string, to string) ([]models.InternetData, error) {
|
||||
whereFilter, whereValues := whereStatement("CreationDate", from, to)
|
||||
rows, err := DB.QueryContext(ctx, `SELECT
|
||||
ID,
|
||||
CreationDate,
|
||||
DLSpeed,
|
||||
ULSpeed
|
||||
FROM internetData `+whereFilter, whereValues...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
data := []models.InternetData{}
|
||||
for rows.Next() {
|
||||
internetData := models.InternetData{}
|
||||
|
||||
err = rows.Scan(&internetData.ID, &internetData.CreationDate, &internetData.DLSpeed, &internetData.ULSpeed)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data = append(data, internetData)
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func GetLatencyData(ctx context.Context, from string, to string) ([]models.InternetData, error) {
|
||||
whereFilter, whereValues := whereStatement("CreationDate", from, to)
|
||||
rows, err := DB.QueryContext(ctx, `SELECT
|
||||
ID,
|
||||
CreationDate,
|
||||
Latency,
|
||||
MaxLatency,
|
||||
MinLatency,
|
||||
Jitter
|
||||
FROM internetData `+whereFilter, whereValues...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
data := []models.InternetData{}
|
||||
for rows.Next() {
|
||||
internetData := models.InternetData{}
|
||||
|
||||
err = rows.Scan(&internetData.ID, &internetData.CreationDate, &internetData.Latency, &internetData.MaxLatency, &internetData.MinLatency, &internetData.Jitter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data = append(data, internetData)
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func GetDurationData(ctx context.Context, from string, to string) ([]models.InternetData, error) {
|
||||
whereFilter, whereValues := whereStatement("CreationDate", from, to)
|
||||
rows, err := DB.QueryContext(ctx, `SELECT
|
||||
ID,
|
||||
CreationDate,
|
||||
TestDuration
|
||||
FROM internetData `+whereFilter, whereValues...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
data := []models.InternetData{}
|
||||
for rows.Next() {
|
||||
internetData := models.InternetData{}
|
||||
|
||||
err = rows.Scan(&internetData.ID, &internetData.CreationDate, &internetData.TestDuration)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data = append(data, internetData)
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func GetAllErrors(ctx context.Context) ([]models.Error, error) {
|
||||
rows, err := DB.QueryContext(ctx, `SELECT
|
||||
ID,
|
||||
|
23
database/helper.go
Normal file
23
database/helper.go
Normal file
@ -0,0 +1,23 @@
|
||||
package database
|
||||
|
||||
func whereStatement(dateCol string, from string, to string) (string, []any) {
|
||||
whereValues := []any{}
|
||||
if from == "" && to == "" {
|
||||
return "", whereValues
|
||||
}
|
||||
|
||||
statement := "WHERE "
|
||||
if from != "" {
|
||||
statement += dateCol + " >= date(?) "
|
||||
whereValues = append(whereValues, from)
|
||||
}
|
||||
if from != "" && to != "" {
|
||||
statement += " AND "
|
||||
}
|
||||
if to != "" {
|
||||
statement += dateCol + " <= date(?)"
|
||||
whereValues = append(whereValues, to)
|
||||
}
|
||||
|
||||
return statement, whereValues
|
||||
}
|
2
go.mod
2
go.mod
@ -1,4 +1,4 @@
|
||||
module gitea.zokki.net/zokki/speedtester
|
||||
module gitea.zokki.net/zokki/speed-tester
|
||||
|
||||
go 1.23.2
|
||||
|
||||
|
153
js/index.ts
153
js/index.ts
@ -1,31 +1,116 @@
|
||||
import Chart from 'chart.js/auto';
|
||||
import Chart, { ChartOptions } from 'chart.js/auto';
|
||||
import zoomPlugin from 'chartjs-plugin-zoom';
|
||||
|
||||
fetch('/data')
|
||||
.then(res => res.json())
|
||||
.then(json => {
|
||||
const downloadData = json.map(j => j.DLSpeed / 125000.0); // Mbps
|
||||
const uploadData = json.map(j => j.ULSpeed / 125000.0); // Mbps
|
||||
const time = json.map(j => j.CreationDate);
|
||||
const latencyData = json.map(j => j.Latency / 1000000); // ms
|
||||
const maxLatencyData = json.map(j => j.MaxLatency / 1000000); // ms
|
||||
const minLatencyData = json.map(j => j.MinLatency / 1000000); // ms
|
||||
const jitterData = json.map(j => j.Jitter / 1000000); // ms
|
||||
const durationData = json.map(j => j.TestDuration / 1000000000); // sec
|
||||
Chart.register(zoomPlugin);
|
||||
|
||||
new Chart(document.querySelector('#up-down-chart') as any, {
|
||||
type: 'line',
|
||||
data: {
|
||||
const lastDay = new Date();
|
||||
lastDay.setDate(lastDay.getDate() - 1);
|
||||
const lastHours = `${lastDay.getFullYear()}-${lastDay.getMonth() + 1}-${String(lastDay.getDate()).padStart(2, '0')}`;
|
||||
|
||||
const options = {
|
||||
plugins: {
|
||||
zoom: {
|
||||
zoom: {
|
||||
drag: { enabled: true },
|
||||
mode: 'x',
|
||||
},
|
||||
},
|
||||
},
|
||||
} satisfies ChartOptions<'line'>;
|
||||
|
||||
const speedChart = new Chart(document.querySelector('#speed-chart') as any, {
|
||||
type: 'line',
|
||||
data: { datasets: [] },
|
||||
options,
|
||||
});
|
||||
|
||||
const latencyChart = new Chart(document.querySelector('#latency-chart') as any, {
|
||||
type: 'line',
|
||||
data: { datasets: [] },
|
||||
options,
|
||||
});
|
||||
|
||||
const durationChart = new Chart(document.querySelector('#duration-chart') as any, {
|
||||
type: 'line',
|
||||
data: { datasets: [] },
|
||||
options,
|
||||
});
|
||||
|
||||
document.querySelectorAll('.reset-chart').forEach(el => {
|
||||
const chartCanvas = getCanvas(el);
|
||||
if (!chartCanvas) return;
|
||||
el.addEventListener('click', () => Chart.getChart(chartCanvas)?.resetZoom('none'));
|
||||
});
|
||||
|
||||
document.querySelectorAll('.last-hours').forEach(el => {
|
||||
const canvasId = getCanvas(el)?.id;
|
||||
if (!canvasId) return;
|
||||
el.addEventListener('click', () => {
|
||||
switch (canvasId) {
|
||||
case 'speed-chart':
|
||||
return updateSpeedChart(lastHours);
|
||||
case 'latency-chart':
|
||||
return updateLatencyChart(lastHours);
|
||||
case 'duration-chart':
|
||||
return updateDurationChart(lastHours);
|
||||
default:
|
||||
return console.warn('[WARNING] `canvasId` not recognized', canvasId);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelectorAll('.complete-data').forEach(el => {
|
||||
const canvasId = getCanvas(el)?.id;
|
||||
if (!canvasId) return;
|
||||
el.addEventListener('click', () => {
|
||||
switch (canvasId) {
|
||||
case 'speed-chart':
|
||||
return updateSpeedChart();
|
||||
case 'latency-chart':
|
||||
return updateLatencyChart();
|
||||
case 'duration-chart':
|
||||
return updateDurationChart();
|
||||
default:
|
||||
return console.warn('[WARNING] `canvasId` not recognized', canvasId);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
updateSpeedChart(lastHours);
|
||||
updateLatencyChart(lastHours);
|
||||
updateDurationChart(lastHours);
|
||||
|
||||
function updateSpeedChart(from?: string, to?: string): void {
|
||||
fetch('/data/speed' + toUrlQuery(from, to))
|
||||
.then(res => res.json())
|
||||
.then(json => {
|
||||
const downloadData = json.map(j => j.DLSpeed / 125000.0); // Mbps
|
||||
const uploadData = json.map(j => j.ULSpeed / 125000.0); // Mbps
|
||||
const time = json.map(j => j.CreationDate);
|
||||
|
||||
speedChart.data = {
|
||||
labels: time,
|
||||
datasets: [
|
||||
{ data: downloadData, label: 'Download' },
|
||||
{ data: uploadData, label: 'Upload' },
|
||||
],
|
||||
},
|
||||
};
|
||||
speedChart.update('none');
|
||||
speedChart.resetZoom('none');
|
||||
});
|
||||
}
|
||||
|
||||
new Chart(document.querySelector('#latency-chart') as any, {
|
||||
type: 'line',
|
||||
data: {
|
||||
function updateLatencyChart(from?: string, to?: string): void {
|
||||
fetch('/data/latency' + toUrlQuery(from, to))
|
||||
.then(res => res.json())
|
||||
.then(json => {
|
||||
const latencyData = json.map(j => j.Latency / 1000000); // ms
|
||||
const maxLatencyData = json.map(j => j.MaxLatency / 1000000); // ms
|
||||
const minLatencyData = json.map(j => j.MinLatency / 1000000); // ms
|
||||
const jitterData = json.map(j => j.Jitter / 1000000); // ms
|
||||
const time = json.map(j => j.CreationDate);
|
||||
|
||||
latencyChart.data = {
|
||||
labels: time,
|
||||
datasets: [
|
||||
{ data: latencyData, label: 'Latency' },
|
||||
@ -33,14 +118,32 @@ fetch('/data')
|
||||
{ data: minLatencyData, label: 'min Latency' },
|
||||
{ data: jitterData, label: 'Jitter' },
|
||||
],
|
||||
},
|
||||
};
|
||||
latencyChart.update('none');
|
||||
latencyChart.resetZoom('none');
|
||||
});
|
||||
}
|
||||
|
||||
new Chart(document.querySelector('#duration-chart') as any, {
|
||||
type: 'line',
|
||||
data: {
|
||||
function updateDurationChart(from?: string, to?: string): void {
|
||||
fetch('/data/duration' + toUrlQuery(from, to))
|
||||
.then(res => res.json())
|
||||
.then(json => {
|
||||
const durationData = json.map(j => j.TestDuration / 1000000000); // sec
|
||||
const time = json.map(j => j.CreationDate);
|
||||
|
||||
durationChart.data = {
|
||||
labels: time,
|
||||
datasets: [{ data: durationData, label: 'Duration' }],
|
||||
},
|
||||
};
|
||||
durationChart.update('none');
|
||||
durationChart.resetZoom('none');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function toUrlQuery(from?: string, to?: string): string {
|
||||
return `?from=${from ?? ''}&to=${to ?? ''}`;
|
||||
}
|
||||
|
||||
function getCanvas(element: Element): HTMLCanvasElement | undefined {
|
||||
return element.closest('.wrapper')?.querySelector('canvas') ?? undefined;
|
||||
}
|
||||
|
16
main.go
16
main.go
@ -4,14 +4,18 @@ import (
|
||||
"embed"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gitea.zokki.net/zokki/speedtester/database"
|
||||
"gitea.zokki.net/zokki/speedtester/routes"
|
||||
"gitea.zokki.net/zokki/speed-tester/database"
|
||||
"gitea.zokki.net/zokki/speed-tester/routes"
|
||||
_ "github.com/mattn/go-sqlite3" // Import the SQLite driver
|
||||
"github.com/showwin/speedtest-go/speedtest"
|
||||
)
|
||||
|
||||
var isDev = strings.ToUpper(os.Getenv("ENVIRONMENT")) == "DEVELOPMENT"
|
||||
|
||||
//go:embed assets
|
||||
var assetFS embed.FS
|
||||
|
||||
@ -34,8 +38,12 @@ func main() {
|
||||
|
||||
router := http.NewServeMux()
|
||||
router.HandleFunc("/", routes.Routes())
|
||||
// router.Handle("/assets/", http.StripPrefix("/assets", http.FileServer(http.Dir("assets"))))
|
||||
router.Handle("/assets/", http.FileServer(http.FS(assetFS)))
|
||||
|
||||
if isDev {
|
||||
router.Handle("/assets/", http.StripPrefix("/assets", http.FileServer(http.Dir("assets"))))
|
||||
} else {
|
||||
router.Handle("/assets/", http.FileServer(http.FS(assetFS)))
|
||||
}
|
||||
|
||||
server := http.Server{
|
||||
Addr: ":9512",
|
||||
|
@ -1,22 +1,22 @@
|
||||
package models
|
||||
|
||||
type InternetData struct {
|
||||
ID uint64
|
||||
CreationDate string
|
||||
URL string
|
||||
Lat string
|
||||
Lon string
|
||||
Name string
|
||||
Country string
|
||||
Sponsor string
|
||||
Host string
|
||||
Distance float64
|
||||
Latency uint64
|
||||
MaxLatency uint64
|
||||
MinLatency uint64
|
||||
Jitter uint64
|
||||
DLSpeed float64
|
||||
ULSpeed float64
|
||||
TestDuration uint64
|
||||
PacketLoss float64
|
||||
ID uint64 `json:"ID,omitempty"`
|
||||
CreationDate string `json:"CreationDate,omitempty"`
|
||||
URL string `json:"URL,omitempty"`
|
||||
Lat string `json:"Lat,omitempty"`
|
||||
Lon string `json:"Lon,omitempty"`
|
||||
Name string `json:"Name,omitempty"`
|
||||
Country string `json:"Country,omitempty"`
|
||||
Sponsor string `json:"Sponsor,omitempty"`
|
||||
Host string `json:"Host,omitempty"`
|
||||
Distance float64 `json:"Distance,omitempty"`
|
||||
Latency uint64 `json:"Latency,omitempty"`
|
||||
MaxLatency uint64 `json:"MaxLatency,omitempty"`
|
||||
MinLatency uint64 `json:"MinLatency,omitempty"`
|
||||
Jitter uint64 `json:"Jitter,omitempty"`
|
||||
DLSpeed float64 `json:"DLSpeed,omitempty"`
|
||||
ULSpeed float64 `json:"ULSpeed,omitempty"`
|
||||
TestDuration uint64 `json:"TestDuration,omitempty"`
|
||||
PacketLoss float64 `json:"PacketLoss,omitempty"`
|
||||
}
|
||||
|
4184
package-lock.json
generated
4184
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,7 @@
|
||||
{
|
||||
"devDependencies": {
|
||||
"chart.js": "^4.4.6",
|
||||
"chartjs-plugin-zoom": "^2.0.1",
|
||||
"esbuild": "^0.24.0",
|
||||
"prettier": "^3.3.3",
|
||||
"prettier-plugin-tailwindcss": "^0.6.8",
|
||||
|
@ -1,12 +1,10 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"gitea.zokki.net/zokki/speedtester/database"
|
||||
"gitea.zokki.net/zokki/speedtester/view"
|
||||
"gitea.zokki.net/zokki/speed-tester/view"
|
||||
)
|
||||
|
||||
type Route struct {
|
||||
@ -30,19 +28,21 @@ func Routes() func(http.ResponseWriter, *http.Request) {
|
||||
SubRoutes: []*Route{
|
||||
{
|
||||
Path: "/data",
|
||||
Get: func(writer http.ResponseWriter, req *http.Request) {
|
||||
data, err := database.GetAllData(req.Context())
|
||||
if err != nil {
|
||||
http.Error(writer, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
Get: allRoute,
|
||||
SubRoutes: []*Route{
|
||||
|
||||
json, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
http.Error(writer, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
writer.Write(json)
|
||||
{
|
||||
Path: "/speed",
|
||||
Get: speedRoute,
|
||||
},
|
||||
{
|
||||
Path: "/latency",
|
||||
Get: latencyRoute,
|
||||
},
|
||||
{
|
||||
Path: "/duration",
|
||||
Get: durationRoute,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
47
routes/data.go
Normal file
47
routes/data.go
Normal file
@ -0,0 +1,47 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"gitea.zokki.net/zokki/speed-tester/database"
|
||||
"gitea.zokki.net/zokki/speed-tester/models"
|
||||
)
|
||||
|
||||
func allRoute(writer http.ResponseWriter, req *http.Request) {
|
||||
query := req.URL.Query()
|
||||
data, err := database.GetAllData(req.Context(), query.Get("from"), query.Get("to"))
|
||||
handleData(writer, data, err)
|
||||
}
|
||||
|
||||
func speedRoute(writer http.ResponseWriter, req *http.Request) {
|
||||
query := req.URL.Query()
|
||||
data, err := database.GetSpeedData(req.Context(), query.Get("from"), query.Get("to"))
|
||||
handleData(writer, data, err)
|
||||
}
|
||||
|
||||
func latencyRoute(writer http.ResponseWriter, req *http.Request) {
|
||||
query := req.URL.Query()
|
||||
data, err := database.GetLatencyData(req.Context(), query.Get("from"), query.Get("to"))
|
||||
handleData(writer, data, err)
|
||||
}
|
||||
|
||||
func durationRoute(writer http.ResponseWriter, req *http.Request) {
|
||||
query := req.URL.Query()
|
||||
data, err := database.GetDurationData(req.Context(), query.Get("from"), query.Get("to"))
|
||||
handleData(writer, data, err)
|
||||
}
|
||||
|
||||
func handleData(writer http.ResponseWriter, data []models.InternetData, err error) {
|
||||
if err != nil {
|
||||
http.Error(writer, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
json, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
http.Error(writer, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
writer.Write(json)
|
||||
}
|
@ -3,8 +3,8 @@ package view
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"gitea.zokki.net/zokki/speedtester/database"
|
||||
view "gitea.zokki.net/zokki/speedtester/view/shared"
|
||||
"gitea.zokki.net/zokki/speed-tester/database"
|
||||
view "gitea.zokki.net/zokki/speed-tester/view/shared"
|
||||
)
|
||||
|
||||
templ Index(ctx context.Context) {
|
||||
@ -12,9 +12,30 @@ templ Index(ctx context.Context) {
|
||||
errors, _ := database.GetAllErrors(ctx)
|
||||
}}
|
||||
@view.Layout() {
|
||||
<canvas id="up-down-chart" class="basis-5/12 flex-shrink-0"></canvas>
|
||||
<canvas id="latency-chart" class="basis-5/12 flex-shrink-0"></canvas>
|
||||
<canvas id="duration-chart" class="basis-5/12 flex-shrink-0"></canvas>
|
||||
<div class="wrapper flex flex-col gap-2">
|
||||
<canvas id="speed-chart"></canvas>
|
||||
<div class="flex gap-4">
|
||||
<button class="reset-chart primary">Reset zoom</button>
|
||||
<button class="last-hours secondary">last hours</button>
|
||||
<button class="complete-data secondary">complete data</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wrapper flex flex-col gap-2">
|
||||
<canvas id="latency-chart"></canvas>
|
||||
<div class="flex gap-4">
|
||||
<button class="reset-chart primary">Reset zoom</button>
|
||||
<button class="last-hours secondary">last hours</button>
|
||||
<button class="complete-data secondary">complete data</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wrapper flex flex-col gap-2">
|
||||
<canvas id="duration-chart"></canvas>
|
||||
<div class="flex gap-4">
|
||||
<button class="reset-chart primary">Reset zoom</button>
|
||||
<button class="last-hours secondary">last hours</button>
|
||||
<button class="complete-data secondary">complete data</button>
|
||||
</div>
|
||||
</div>
|
||||
<ul>
|
||||
<caption>Errors:</caption>
|
||||
for _, error := range errors {
|
||||
|
@ -1,91 +0,0 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.2.778
|
||||
package view
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
import "github.com/a-h/templ"
|
||||
import templruntime "github.com/a-h/templ/runtime"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"gitea.zokki.net/zokki/speedtester/database"
|
||||
view "gitea.zokki.net/zokki/speedtester/view/shared"
|
||||
)
|
||||
|
||||
func Index(ctx context.Context) templ.Component {
|
||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||
return templ_7745c5c3_CtxErr
|
||||
}
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var1 == nil {
|
||||
templ_7745c5c3_Var1 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
|
||||
errors, _ := database.GetAllErrors(ctx)
|
||||
templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 1)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
for _, error := range errors {
|
||||
log := fmt.Sprintf("[%s] (%s): %s", error.CreationDate, error.Detail, error.Error)
|
||||
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 2)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var3 string
|
||||
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(log)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/index.templ`, Line: 22, Col: 13}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 3)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 4)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
})
|
||||
templ_7745c5c3_Err = view.Layout().Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
})
|
||||
}
|
||||
|
||||
var _ = templruntime.GeneratedTemplate
|
@ -1,4 +0,0 @@
|
||||
<canvas id=\"up-down-chart\" class=\"basis-5/12 flex-shrink-0\"></canvas><canvas id=\"latency-chart\" class=\"basis-5/12 flex-shrink-0\"></canvas><canvas id=\"duration-chart\" class=\"basis-5/12 flex-shrink-0\"></canvas><ul><caption>Errors:</caption>
|
||||
<li>
|
||||
</li>
|
||||
</ul>
|
@ -7,8 +7,8 @@ templ Layout() {
|
||||
<meta charset="utf-8"/>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<link rel="stylesheet" href="/assets/global.css"/>
|
||||
<link rel="stylesheet" href="/assets/styles.css"/>
|
||||
<link rel="stylesheet" href="/assets/global.css"/>
|
||||
<script src="/assets/index.js" defer></script>
|
||||
<title>Internet Speed</title>
|
||||
</head>
|
||||
|
@ -1,48 +0,0 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.2.778
|
||||
package view
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
import "github.com/a-h/templ"
|
||||
import templruntime "github.com/a-h/templ/runtime"
|
||||
|
||||
func Layout() templ.Component {
|
||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||
return templ_7745c5c3_CtxErr
|
||||
}
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var1 == nil {
|
||||
templ_7745c5c3_Var1 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 1)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ_7745c5c3_Var1.Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 2)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
})
|
||||
}
|
||||
|
||||
var _ = templruntime.GeneratedTemplate
|
@ -1,2 +0,0 @@
|
||||
<!doctype html><html lang=\"de\" class=\"bg-background\"><head><meta charset=\"utf-8\"><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><link rel=\"stylesheet\" href=\"/assets/global.css\"><link rel=\"stylesheet\" href=\"/assets/styles.css\"><script src=\"/assets/index.js\" defer></script><title>Internet Speed</title></head><body><main>
|
||||
</main></body></html>
|
@ -159,7 +159,9 @@ body {
|
||||
}
|
||||
|
||||
main {
|
||||
@apply grid flex-wrap justify-around gap-6 p-4 xl:flex;
|
||||
@apply grid p-4;
|
||||
|
||||
row-gap: var(--main-gap, 2.5rem);
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
@ -237,4 +239,12 @@ picture {
|
||||
|
||||
button {
|
||||
@apply flex min-w-fit items-center rounded px-4 py-2;
|
||||
|
||||
&.primary {
|
||||
@apply bg-primary text-on-primary;
|
||||
}
|
||||
|
||||
&.secondary {
|
||||
@apply bg-secondary text-on-secondary;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user