init
This commit is contained in:
parent
c1df0f95c3
commit
f2e2f45a19
21 changed files with 684 additions and 0 deletions
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
/borgmatic-monitor
|
||||
/env.sh
|
||||
/example.json
|
||||
48
cmd/main.go
Normal file
48
cmd/main.go
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/labstack/echo/v5"
|
||||
"github.com/labstack/echo/v5/middleware"
|
||||
"gitnet.fr/deblan/borgmatic-monitor/pkg/database"
|
||||
"gitnet.fr/deblan/borgmatic-monitor/pkg/database/model"
|
||||
"gitnet.fr/deblan/borgmatic-monitor/pkg/web"
|
||||
)
|
||||
|
||||
func main() {
|
||||
database.Init()
|
||||
|
||||
db := database.GetDb()
|
||||
|
||||
db.AutoMigrate(model.Host{})
|
||||
db.AutoMigrate(model.Info{})
|
||||
|
||||
e := echo.New()
|
||||
e.Use(middleware.RequestLogger())
|
||||
|
||||
e.GET("/", web.Hosts)
|
||||
e.GET("/host/:id", web.Host)
|
||||
|
||||
if err := e.Start(":1323"); err != nil {
|
||||
e.Logger.Error("failed to start server", "error", err)
|
||||
}
|
||||
|
||||
// var hosts []model.Host
|
||||
//
|
||||
// db.Model(model.Host{}).Find(&hosts)
|
||||
//
|
||||
// for _, host := range hosts {
|
||||
// fmt.Printf("%+v\n", host)
|
||||
// }
|
||||
|
||||
// if content, err := ioutil.ReadFile("example.json"); err == nil {
|
||||
// var res borgmatic.Infos
|
||||
//
|
||||
// err := json.Unmarshal(content, &res)
|
||||
//
|
||||
// if err != nil {
|
||||
// fmt.Printf("%+v\n", err)
|
||||
// }
|
||||
//
|
||||
// fmt.Printf("%+v\n", res)
|
||||
// }
|
||||
}
|
||||
15
go.mod
15
go.mod
|
|
@ -1,3 +1,18 @@
|
|||
module gitnet.fr/deblan/borgmatic-monitor
|
||||
|
||||
go 1.25.1
|
||||
|
||||
require (
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/go-sql-driver/mysql v1.8.1 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/labstack/echo/v5 v5.0.4 // indirect
|
||||
github.com/spf13/cast v1.10.0 // indirect
|
||||
golang.org/x/text v0.34.0 // indirect
|
||||
golang.org/x/time v0.14.0 // indirect
|
||||
gorm.io/driver/mysql v1.6.0 // indirect
|
||||
gorm.io/gorm v1.31.1 // indirect
|
||||
maragu.dev/gomponents v1.2.0 // indirect
|
||||
)
|
||||
|
|
|
|||
24
go.sum
Normal file
24
go.sum
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/labstack/echo/v5 v5.0.4 h1:ll3I/O8BifjMztj9dD1vx/peZQv8cR2CTUdQK6QxGGc=
|
||||
github.com/labstack/echo/v5 v5.0.4/go.mod h1:SyvlSdObGjRXeQfCCXW/sybkZdOOQZBmpKF0bvALaeo=
|
||||
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
|
||||
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||
gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg=
|
||||
gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo=
|
||||
gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg=
|
||||
gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
|
||||
maragu.dev/gomponents v1.2.0 h1:H7/N5htz1GCnhu0HB1GasluWeU2rJZOYztVEyN61iTc=
|
||||
maragu.dev/gomponents v1.2.0/go.mod h1:oEDahza2gZoXDoDHhw8jBNgH+3UR5ni7Ur648HORydM=
|
||||
15
pkg/borgmatic/archive.go
Normal file
15
pkg/borgmatic/archive.go
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
package borgmatic
|
||||
|
||||
type Archive struct {
|
||||
CommandLine []string `json:"command_line"`
|
||||
Comment string `json:"comment"`
|
||||
Duration float64 `json:"duration"`
|
||||
Start *Time `json:"start"`
|
||||
End *Time `json:"end"`
|
||||
Hostname string `json:"hostname"`
|
||||
Id string `json:"id"`
|
||||
Limits ArchiveLimits `json:"limits"`
|
||||
Stats ArchiveStats `json:"stats"`
|
||||
Name string `json:"name"`
|
||||
Username string `json:"username"`
|
||||
}
|
||||
5
pkg/borgmatic/archive_limits.go
Normal file
5
pkg/borgmatic/archive_limits.go
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
package borgmatic
|
||||
|
||||
type ArchiveLimits struct {
|
||||
MaxArchiveSize float64 `json:"max_archive_size"`
|
||||
}
|
||||
8
pkg/borgmatic/archive_stats.go
Normal file
8
pkg/borgmatic/archive_stats.go
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
package borgmatic
|
||||
|
||||
type ArchiveStats struct {
|
||||
CompressedSize int64 `json:"compressed_size"`
|
||||
DeduplicatedSize int64 `json:"deduplicated_size"`
|
||||
Nfiles int64 `json:"nfiles"`
|
||||
OriginalSize int64 `json:"original_size"`
|
||||
}
|
||||
6
pkg/borgmatic/cache.go
Normal file
6
pkg/borgmatic/cache.go
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
package borgmatic
|
||||
|
||||
type Cache struct {
|
||||
Path string `json:"path"`
|
||||
Stats CacheStats `json:"stats"`
|
||||
}
|
||||
10
pkg/borgmatic/cache_stats.go
Normal file
10
pkg/borgmatic/cache_stats.go
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
package borgmatic
|
||||
|
||||
type CacheStats struct {
|
||||
TotalChunks float64 `json:"total_chunks"`
|
||||
TotalCsize float64 `json:"total_csize"`
|
||||
TotalSize float64 `json:"total_size"`
|
||||
TotalUniqueChunks float64 `json:"total_unique_chunks"`
|
||||
UniqueCsize float64 `json:"unique_csize"`
|
||||
UniqueSize float64 `json:"unique_size"`
|
||||
}
|
||||
5
pkg/borgmatic/encryption.go
Normal file
5
pkg/borgmatic/encryption.go
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
package borgmatic
|
||||
|
||||
type Encryption struct {
|
||||
Mode string `json:"mode"`
|
||||
}
|
||||
8
pkg/borgmatic/info.go
Normal file
8
pkg/borgmatic/info.go
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
package borgmatic
|
||||
|
||||
type Info struct {
|
||||
Archives []Archive `json:"archives"`
|
||||
Cache Cache `json:"cache"`
|
||||
Encryption Encryption `json:"encryption"`
|
||||
Repository Repository `json:"repository"`
|
||||
}
|
||||
3
pkg/borgmatic/infos.go
Normal file
3
pkg/borgmatic/infos.go
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
package borgmatic
|
||||
|
||||
type Infos []Info
|
||||
8
pkg/borgmatic/repository.go
Normal file
8
pkg/borgmatic/repository.go
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
package borgmatic
|
||||
|
||||
type Repository struct {
|
||||
Id string `json:"id"`
|
||||
LastModified *Time `json:"last_modified"`
|
||||
Location string `json:"location"`
|
||||
Label string `json:"label"`
|
||||
}
|
||||
24
pkg/borgmatic/time.go
Normal file
24
pkg/borgmatic/time.go
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
package borgmatic
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Time struct {
|
||||
time.Time
|
||||
}
|
||||
|
||||
func (t *Time) UnmarshalJSON(b []byte) (err error) {
|
||||
s := strings.Trim(string(b), "\"")
|
||||
|
||||
if s == "null" {
|
||||
t.Time = time.Time{}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
t.Time, err = time.Parse("2006-01-02T15:04:05.000000", s)
|
||||
|
||||
return
|
||||
}
|
||||
68
pkg/database/database.go
Normal file
68
pkg/database/database.go
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
package database
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
var (
|
||||
db *gorm.DB
|
||||
config mysql.Config
|
||||
)
|
||||
|
||||
func Init() {
|
||||
config = mysql.Config{
|
||||
DSN: fmt.Sprintf(
|
||||
"%s:%s@tcp(%s:%s)/%s?charset=utf8&parseTime=True&loc=Local",
|
||||
fallback(os.Getenv("MYSQL_USERNAME"), "root"),
|
||||
fallback(os.Getenv("MYSQL_PASSWORD"), "root"),
|
||||
fallback(os.Getenv("MYSQL_HOST"), "127.0.0.1"),
|
||||
fallback(os.Getenv("MYSQL_PORT"), "3306"),
|
||||
fallback(os.Getenv("MYSQL_DATABASE"), "app"),
|
||||
),
|
||||
DefaultStringSize: 256,
|
||||
DisableDatetimePrecision: true,
|
||||
DontSupportRenameIndex: true,
|
||||
DontSupportRenameColumn: true,
|
||||
SkipInitializeWithVersion: false,
|
||||
}
|
||||
}
|
||||
|
||||
func GetDb() *gorm.DB {
|
||||
var err error
|
||||
|
||||
if db == nil {
|
||||
db, err = gorm.Open(mysql.New(config))
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
gdb, err := db.DB()
|
||||
|
||||
if err != nil {
|
||||
db = nil
|
||||
|
||||
return GetDb()
|
||||
}
|
||||
|
||||
if err = gdb.Ping(); err != nil {
|
||||
db = nil
|
||||
|
||||
return GetDb()
|
||||
}
|
||||
|
||||
return db
|
||||
}
|
||||
|
||||
func fallback(value string, fb string) string {
|
||||
if value == "" {
|
||||
return fb
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
39
pkg/database/host.go
Normal file
39
pkg/database/host.go
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
package database
|
||||
|
||||
import (
|
||||
"gitnet.fr/deblan/borgmatic-monitor/pkg/database/model"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func Hosts() []model.Host {
|
||||
var hosts []model.Host
|
||||
|
||||
GetDb().
|
||||
Model(model.Host{}).
|
||||
Preload("Infos", func(db *gorm.DB) *gorm.DB {
|
||||
return db.Order("infos.id DESC")
|
||||
}).
|
||||
Order("name ASC").
|
||||
Find(&hosts)
|
||||
|
||||
return hosts
|
||||
}
|
||||
|
||||
func Host(id int) *model.Host {
|
||||
var host model.Host
|
||||
|
||||
GetDb().
|
||||
Model(model.Host{}).
|
||||
Preload("Infos", func(db *gorm.DB) *gorm.DB {
|
||||
return db.Order("infos.id DESC")
|
||||
}).
|
||||
Order("name ASC").
|
||||
Where("id = ?", id).
|
||||
First(&host)
|
||||
|
||||
if host.ID > 0 {
|
||||
return &host
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
50
pkg/database/model/host.go
Normal file
50
pkg/database/model/host.go
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
package model
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gitnet.fr/deblan/borgmatic-monitor/pkg/borgmatic"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Host struct {
|
||||
gorm.Model
|
||||
|
||||
Name string `gorm:"not null"`
|
||||
Token string `gorm:"not null"`
|
||||
Infos []Info `gorm:"one2many:info;not null"`
|
||||
}
|
||||
|
||||
func (h *Host) ArchivesCount() int {
|
||||
c := 0
|
||||
|
||||
for _, item := range h.Infos {
|
||||
if infos := *item.Infos(); infos != nil {
|
||||
for _, info := range *item.Infos() {
|
||||
c += len(info.Archives)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return c
|
||||
|
||||
}
|
||||
|
||||
func (h *Host) LastArchive() *borgmatic.Archive {
|
||||
var last *borgmatic.Archive
|
||||
var lastDate *time.Time
|
||||
|
||||
for _, item := range h.Infos {
|
||||
if infos := *item.Infos(); infos != nil {
|
||||
for _, info := range infos {
|
||||
for _, a := range info.Archives {
|
||||
if lastDate == nil || a.End.After(*lastDate) {
|
||||
last = &a
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return last
|
||||
}
|
||||
28
pkg/database/model/info.go
Normal file
28
pkg/database/model/info.go
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"gitnet.fr/deblan/borgmatic-monitor/pkg/borgmatic"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Info struct {
|
||||
gorm.Model
|
||||
|
||||
HostID uint
|
||||
Host Host `gorm:"many2one:info"`
|
||||
Data string `gorm:"type:text;not null"`
|
||||
}
|
||||
|
||||
func (i *Info) Infos() *borgmatic.Infos {
|
||||
var infos borgmatic.Infos
|
||||
|
||||
err := json.Unmarshal([]byte(i.Data), &infos)
|
||||
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &infos
|
||||
}
|
||||
137
pkg/web/host.go
Normal file
137
pkg/web/host.go
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
package web
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/labstack/echo/v5"
|
||||
"github.com/spf13/cast"
|
||||
"gitnet.fr/deblan/borgmatic-monitor/pkg/borgmatic"
|
||||
"gitnet.fr/deblan/borgmatic-monitor/pkg/database"
|
||||
|
||||
. "maragu.dev/gomponents"
|
||||
|
||||
// . "maragu.dev/gomponents/components"
|
||||
. "maragu.dev/gomponents/html"
|
||||
)
|
||||
|
||||
func Host(c *echo.Context) error {
|
||||
id := cast.ToInt(c.Param("id"))
|
||||
|
||||
if id == 0 {
|
||||
return c.HTML(http.StatusNotFound, "Not found.")
|
||||
}
|
||||
|
||||
host := database.Host(id)
|
||||
|
||||
if host == nil {
|
||||
return c.HTML(http.StatusNotFound, "Not found.")
|
||||
}
|
||||
|
||||
var nodes []Node
|
||||
|
||||
if len(host.Infos) == 0 {
|
||||
nodes = append(
|
||||
nodes,
|
||||
Div(
|
||||
Class("text-muted"),
|
||||
Text("Nothing yet!"),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
|
||||
for _, item := range host.Infos {
|
||||
if infos := *item.Infos(); infos != nil {
|
||||
for i := len(infos) - 1; i >= 0; i-- {
|
||||
info := infos[i]
|
||||
|
||||
card := Div(
|
||||
Class("col-12"),
|
||||
Div(
|
||||
Class("card mb-3"),
|
||||
Div(
|
||||
Class("card-header"),
|
||||
Text(info.Repository.LastModified.Format("2006-01-02 15:04:05")),
|
||||
),
|
||||
Div(
|
||||
Class("card-body"),
|
||||
Div(
|
||||
I(Class("ri-price-tag-3-line me-1")),
|
||||
Text(info.Repository.Label),
|
||||
),
|
||||
Div(
|
||||
I(Class("ri-map-pin-line me-1")),
|
||||
Text(info.Repository.Location),
|
||||
),
|
||||
Div(
|
||||
Class("mb-3"),
|
||||
I(Class("ri-lock-line me-1")),
|
||||
Text(info.Encryption.Mode),
|
||||
),
|
||||
Div(
|
||||
Map(info.Archives, func(archive borgmatic.Archive) Node {
|
||||
return Div(
|
||||
Class("row"),
|
||||
Div(
|
||||
Class("col-12 col-md-4"),
|
||||
I(Class("ri-store-3-fill me-1")),
|
||||
Text(archive.Name),
|
||||
),
|
||||
Div(
|
||||
Class("text-end col-12 col-md-4"),
|
||||
Text(fmt.Sprintf("%2.fs", archive.Duration)),
|
||||
I(Class("ri-time-fill mx-2")),
|
||||
Text(fmt.Sprintf("%d", archive.Stats.Nfiles)),
|
||||
I(Class("ri-file-fill ms-2")),
|
||||
),
|
||||
Div(
|
||||
Class("text-end col-12 col-md-4"),
|
||||
Div(
|
||||
Class("row"),
|
||||
Div(
|
||||
Class("col-4 text-secondary"),
|
||||
Text(humanize.Bytes(uint64(archive.Stats.OriginalSize))),
|
||||
),
|
||||
Div(
|
||||
Class("col-4 text-primary"),
|
||||
Text(humanize.Bytes(uint64(archive.Stats.DeduplicatedSize))),
|
||||
),
|
||||
Div(
|
||||
Class("col-4 text-success"),
|
||||
Text(humanize.Bytes(uint64(archive.Stats.CompressedSize))),
|
||||
I(Class("ri-file-zip-fill ms-2")),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
nodes = append(nodes, card)
|
||||
|
||||
// for _, archive := range info.Archives {
|
||||
//
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return c.HTML(http.StatusOK, page(
|
||||
fmt.Sprintf("host_%d", host.ID),
|
||||
host.Name,
|
||||
Div(
|
||||
Class("container-fluid"),
|
||||
Div(
|
||||
Class("row"),
|
||||
Map(nodes, func(node Node) Node {
|
||||
return node
|
||||
}),
|
||||
),
|
||||
),
|
||||
))
|
||||
}
|
||||
86
pkg/web/hosts.go
Normal file
86
pkg/web/hosts.go
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
package web
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/labstack/echo/v5"
|
||||
"gitnet.fr/deblan/borgmatic-monitor/pkg/database"
|
||||
. "maragu.dev/gomponents"
|
||||
|
||||
// . "maragu.dev/gomponents/components"
|
||||
. "maragu.dev/gomponents/html"
|
||||
)
|
||||
|
||||
func Hosts(c *echo.Context) error {
|
||||
var nodes []Node
|
||||
|
||||
for _, host := range database.Hosts() {
|
||||
lastArchive := host.LastArchive()
|
||||
|
||||
var body Node
|
||||
|
||||
if lastArchive != nil {
|
||||
body = Div(
|
||||
Div(
|
||||
Class("fw-bold"),
|
||||
I(Class("ri-archive-stack-line me-1")),
|
||||
Text(fmt.Sprintf(
|
||||
"%d archive(s)",
|
||||
host.ArchivesCount(),
|
||||
)),
|
||||
),
|
||||
Div(
|
||||
I(Class("ri-calendar-line me-1")),
|
||||
Text(fmt.Sprintf(
|
||||
"Last update on %s",
|
||||
lastArchive.End.Format("2006-01-02 15:04:05"),
|
||||
)),
|
||||
),
|
||||
Div(
|
||||
Class("mt-3"),
|
||||
A(
|
||||
Class("btn btn-primary"),
|
||||
Href(fmt.Sprintf("/host/%d", host.ID)),
|
||||
Text("Show"),
|
||||
),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
body = Div(
|
||||
Class("text-danger"),
|
||||
I(Class("ri-error-warning-line me-1")),
|
||||
Text("Nothing yet!"),
|
||||
)
|
||||
}
|
||||
|
||||
card := Div(
|
||||
Class("col-6"),
|
||||
Div(
|
||||
Class("card mb-3"),
|
||||
Div(
|
||||
Class("card-header"),
|
||||
Text(host.Name),
|
||||
),
|
||||
Div(
|
||||
Class("card-body"),
|
||||
body,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
nodes = append(nodes, card)
|
||||
}
|
||||
|
||||
return c.HTML(http.StatusOK, page(
|
||||
"home",
|
||||
"Hosts",
|
||||
Div(
|
||||
Class("container-fluid"),
|
||||
Div(
|
||||
Class("row"),
|
||||
Group(nodes),
|
||||
),
|
||||
),
|
||||
))
|
||||
}
|
||||
94
pkg/web/page.go
Normal file
94
pkg/web/page.go
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
package web
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"gitnet.fr/deblan/borgmatic-monitor/pkg/database"
|
||||
. "maragu.dev/gomponents"
|
||||
. "maragu.dev/gomponents/components"
|
||||
. "maragu.dev/gomponents/html"
|
||||
)
|
||||
|
||||
func page(currentNode string, title string, children ...Node) string {
|
||||
page := HTML5(HTML5Props{
|
||||
Title: title,
|
||||
Language: "en",
|
||||
Head: []Node{
|
||||
Link(
|
||||
Href("https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css"),
|
||||
Rel("stylesheet"),
|
||||
Integrity("sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB"),
|
||||
CrossOrigin("anonymous"),
|
||||
),
|
||||
Link(
|
||||
Href("https://cdn.jsdelivr.net/npm/remixicon@4.9.0/fonts/remixicon.css"),
|
||||
Rel("stylesheet"),
|
||||
),
|
||||
},
|
||||
Body: []Node{
|
||||
Main(
|
||||
Class("d-flex flex-nowrap"),
|
||||
menu(currentNode),
|
||||
Div(
|
||||
Class("flex-fill p-3"),
|
||||
Style("max-height: 100vh; overflow: auto"),
|
||||
Group(children),
|
||||
),
|
||||
),
|
||||
Script(
|
||||
Src("https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js"),
|
||||
Integrity("sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI"),
|
||||
),
|
||||
},
|
||||
})
|
||||
|
||||
var buf bytes.Buffer
|
||||
|
||||
page.Render(&buf)
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func menu(currentNode string) Node {
|
||||
items := [][3]string{
|
||||
[3]string{"home", "/", "All"},
|
||||
}
|
||||
|
||||
for _, host := range database.Hosts() {
|
||||
items = append(
|
||||
items,
|
||||
[3]string{
|
||||
fmt.Sprintf("host_%d", host.ID),
|
||||
fmt.Sprintf("/host/%d", host.ID),
|
||||
host.Name,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
return Div(
|
||||
Class("d-flex flex-column flex-shrink-0 p-3 text-bg-dark"),
|
||||
Style("width: 280px; height: 100vh"),
|
||||
Ul(
|
||||
Class("nav nav-pills flex-column mb-auto"),
|
||||
Map(items, func(item [3]string) Node {
|
||||
class := "nav-link"
|
||||
|
||||
if currentNode == item[0] {
|
||||
class += " active"
|
||||
} else {
|
||||
class += " text-white"
|
||||
}
|
||||
|
||||
return Li(
|
||||
Class("nav-item"),
|
||||
A(
|
||||
Class(class),
|
||||
Href(item[1]),
|
||||
Text(item[2]),
|
||||
),
|
||||
)
|
||||
}),
|
||||
),
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue