merge barnch switch_to_templ
- Moved to templ for templating - Started adding cluster statistics
10
.air.toml
@ -5,23 +5,23 @@ tmp_dir = "tmp"
|
||||
[build]
|
||||
args_bin = []
|
||||
bin = "./tmp/main"
|
||||
cmd = "go build -o ./tmp/main ."
|
||||
pre_cmd = [""]
|
||||
cmd = "templ generate && go build -o ./tmp/main ."
|
||||
delay = 1000
|
||||
exclude_dir = ["tmp", "vendor", "testdata", "assets/node_modules"]
|
||||
exclude_dir = ["tmp", "vendor", "testdata", "node_modules", "public"]
|
||||
exclude_file = []
|
||||
exclude_regex = ["_test.go"]
|
||||
exclude_regex = ["_test.go", "_templ.go"]
|
||||
exclude_unchanged = false
|
||||
follow_symlink = false
|
||||
full_bin = ""
|
||||
include_dir = []
|
||||
include_ext = ["go", "tpl", "tmpl", "html", "gohtml", "css"]
|
||||
include_ext = ["go", "css", "templ", "config.js"]
|
||||
include_file = []
|
||||
kill_delay = "0s"
|
||||
log = "build-errors.log"
|
||||
poll = false
|
||||
poll_interval = 0
|
||||
post_cmd = []
|
||||
pre_cmd = []
|
||||
rerun = false
|
||||
rerun_delay = 500
|
||||
send_interrupt = false
|
||||
|
25
README.md
@ -72,3 +72,28 @@ Overall goals:
|
||||
</clustvirt:data>
|
||||
</metadata>
|
||||
```
|
||||
|
||||
### Open Source Projects
|
||||
|
||||
The following projects are being used as part of ClustVirt.
|
||||
Only those being used directly are listed, not the dependencies.
|
||||
|
||||
#### Go
|
||||
|
||||
| Library | Use | License |
|
||||
| --- | --- | --- |
|
||||
| [xdg](https://github.com/adrg/xdg) | XDG paths | MIT |
|
||||
| [spf13/viper](https://github.com/spf13/viper) | Configuration | MIT |
|
||||
| [spf13](https://github.com/spf13/cobra) | Command Pattern CLI | Apache 2.0 |
|
||||
| [go-chart/v2](https://github.com/wcharczuk/go-chart) | Chart generation | MIT |
|
||||
| [libvirt](https://libvirt.org/go/libvirt) | Communications with Libvirtd | MIT |
|
||||
| [libvirtxml](https://libvirt.org/go/libvirtxml) | Libvirt XML parsing and generation | MIT |
|
||||
| [templ](https://github.com/a-h/templ) | HTML Templating Engine | MIT |
|
||||
| [chi/v5](https://github.com/go-chi/chi) | HTTP Routing | MIT |
|
||||
|
||||
#### Node NPM
|
||||
|
||||
| Library | Use | License |
|
||||
| --- | --- | ------- |
|
||||
| [tailwindcss](https://tailwindcss.com) | CSS UI library | MIT |
|
||||
| [tailwindcss/forms](https://tailwindcss.com) | CSS UI library for forms | MIT |
|
||||
|
@ -1,10 +0,0 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@tailwind variants;
|
||||
|
||||
@layer components {
|
||||
.notimpl {
|
||||
@apply italic font-light list-image-possible;
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: ["../view/**/*.gohtml"],
|
||||
theme: {
|
||||
extend: {
|
||||
listStyleImage: {
|
||||
never: 'url("/static/icons/list-never.svg")',
|
||||
accepted: 'url("/static/icons/list-accepted.svg")',
|
||||
possible: 'url("/static/icons/list-possible.svg")',
|
||||
informational: 'url("/static/icons/list-informational.svg")',
|
||||
},
|
||||
content: {
|
||||
link: 'url("/static/icons/link.svg")',
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
require('@tailwindcss/forms'),
|
||||
require('tailwind-dracula')(),
|
||||
],
|
||||
}
|
||||
|
1
cluster/api.go
Normal file
@ -0,0 +1 @@
|
||||
package cluster
|
1
cluster/auth.go
Normal file
@ -0,0 +1 @@
|
||||
package cluster
|
69
cluster/builder.go
Normal file
@ -0,0 +1,69 @@
|
||||
package cluster
|
||||
|
||||
import (
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"git.staur.ca/stobbsm/clustvirt/lib/host"
|
||||
)
|
||||
|
||||
// ClusterBuilder is used to build a Cluster object, which can then be used
|
||||
type ClusterBuilder struct {
|
||||
cluster *Cluster
|
||||
}
|
||||
|
||||
// New starts the builder pattern for the Cluster.
|
||||
// Sets the default interval time to 30 seconds.
|
||||
func New() *ClusterBuilder {
|
||||
return &ClusterBuilder{
|
||||
cluster: &Cluster{
|
||||
interval: time.Duration(time.Second * 30),
|
||||
hosts: map[string]*host.Host{},
|
||||
defaultURI: host.URI_QEMU_SSH_SYSTEM,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// SetInterval sets the check interval of the Cluster being built.
|
||||
func (c *ClusterBuilder) SetInterval(i time.Duration) *ClusterBuilder {
|
||||
c.cluster.interval = i
|
||||
return c
|
||||
}
|
||||
|
||||
// Build retuns the built cluster
|
||||
func (c *ClusterBuilder) Build() *Cluster {
|
||||
return c.cluster
|
||||
}
|
||||
|
||||
func (c *ClusterBuilder) DefaultHostURI(uri *host.URI) *ClusterBuilder {
|
||||
c.cluster.defaultURI = uri
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *ClusterBuilder) AddHost(h string) *ClusterBuilder {
|
||||
if _, ok := c.cluster.hosts[h]; ok {
|
||||
log.Println("warning: trying to add duplicate host")
|
||||
return c
|
||||
}
|
||||
hc, err := host.ConnectHost(c.cluster.defaultURI, h)
|
||||
if err != nil {
|
||||
log.Printf("failed to connect to host: %s, %s", h, err)
|
||||
return c
|
||||
}
|
||||
c.cluster.hosts[h] = hc
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *ClusterBuilder) AddHostWithURI(h string, uri *host.URI) *ClusterBuilder {
|
||||
if _, ok := c.cluster.hosts[h]; ok {
|
||||
log.Println("warning: trying to add duplicate host")
|
||||
return c
|
||||
}
|
||||
hc, err := host.ConnectHost(uri, h)
|
||||
if err != nil {
|
||||
log.Printf("failed to connect to host: %s, %s", h, err)
|
||||
return c
|
||||
}
|
||||
c.cluster.hosts[h] = hc
|
||||
return c
|
||||
}
|
26
cluster/cluster.go
Normal file
@ -0,0 +1,26 @@
|
||||
// Package cluster is the unique portion of this application that implements
|
||||
// basic cluster controls overtop of Libvirtd hosts. The controller is agnostic
|
||||
// about where it is running, and doesn't need to be running on a host that
|
||||
// has Libvirtd installed on it.
|
||||
//
|
||||
// The cluster can be configured through the use of TOML configuration file,
|
||||
// or with CLI flags. This is done via the itegration of the greate go libraries
|
||||
// spf13/viper and spf13/cobra.
|
||||
package cluster
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"git.staur.ca/stobbsm/clustvirt/lib/host"
|
||||
)
|
||||
|
||||
// Cluster is the data structure and controller for cluster access.
|
||||
// Using it's methods, you can access hosts, virtual machines, health
|
||||
// data, and more.
|
||||
// Cluster implements a time.Ticker that will be used to check the connection
|
||||
// status of hosts, and reconnect if a connection was blocked or interrupted.
|
||||
type Cluster struct {
|
||||
interval time.Duration
|
||||
hosts map[string]*host.Host
|
||||
defaultURI *host.URI
|
||||
}
|
1
cluster/config.go
Normal file
@ -0,0 +1 @@
|
||||
package cluster
|
1
cluster/daemon.go
Normal file
@ -0,0 +1 @@
|
||||
package cluster
|
1
cluster/format/format.go
Normal file
@ -0,0 +1 @@
|
||||
package format
|
1
cluster/format/json.go
Normal file
@ -0,0 +1 @@
|
||||
package format
|
1
cluster/format/xml.go
Normal file
@ -0,0 +1 @@
|
||||
package format
|
1
cluster/host.go
Normal file
@ -0,0 +1 @@
|
||||
package cluster
|
1
cluster/network.go
Normal file
@ -0,0 +1 @@
|
||||
package cluster
|
296
cluster/stats.go
Normal file
@ -0,0 +1,296 @@
|
||||
package cluster
|
||||
|
||||
// ClusterStats is used to gather stats for the entire cluster
|
||||
type ClusterStats struct {
|
||||
CPU struct {
|
||||
Sockets uint32
|
||||
Cores uint32
|
||||
Threads uint32
|
||||
Allocated uint32
|
||||
}
|
||||
Memory struct {
|
||||
Total uint64
|
||||
Free uint64
|
||||
Buffers uint64
|
||||
Cached uint64
|
||||
Allocated uint64
|
||||
}
|
||||
Storage struct {
|
||||
Total uint64
|
||||
Used uint64
|
||||
Free uint64
|
||||
Active uint32
|
||||
Inactive uint32
|
||||
Pools uint32
|
||||
|
||||
Volumes struct {
|
||||
Total uint32
|
||||
Active uint32
|
||||
Inactive uint32
|
||||
}
|
||||
}
|
||||
VM struct {
|
||||
Count uint32
|
||||
Started uint32
|
||||
Stopped uint32
|
||||
}
|
||||
Host struct {
|
||||
Count uint32
|
||||
Available uint32
|
||||
}
|
||||
Network struct {
|
||||
Count uint32
|
||||
Active uint32
|
||||
Inactive uint32
|
||||
}
|
||||
|
||||
old *ClusterStats
|
||||
c *Cluster
|
||||
}
|
||||
|
||||
// ClusterStats is used to gather stats for the entire cluster
|
||||
type StatDiff struct {
|
||||
CPU struct {
|
||||
Sockets int
|
||||
Cores int
|
||||
Threads int
|
||||
Allocated int
|
||||
}
|
||||
Memory struct {
|
||||
Total int
|
||||
Free int
|
||||
Buffers int
|
||||
Cached int
|
||||
Allocated int
|
||||
}
|
||||
Storage struct {
|
||||
Total int
|
||||
Used int
|
||||
Free int
|
||||
Active int
|
||||
Inactive int
|
||||
Pools int
|
||||
|
||||
Volumes struct {
|
||||
Total int
|
||||
Active int
|
||||
Inactive int
|
||||
}
|
||||
}
|
||||
VM struct {
|
||||
Count int
|
||||
Started int
|
||||
Stopped int
|
||||
}
|
||||
Host struct {
|
||||
Count int
|
||||
Available int
|
||||
}
|
||||
Network struct {
|
||||
Count int
|
||||
Active int
|
||||
Inactive int
|
||||
}
|
||||
}
|
||||
|
||||
// InitStats is given a cluster, which it then uses to load the initial statistics
|
||||
// Does not close connections, but uses the host connections available to the
|
||||
// cluster to add statistics together.
|
||||
func InitStats(c *Cluster) *ClusterStats {
|
||||
cs := &ClusterStats{
|
||||
c: c,
|
||||
}
|
||||
|
||||
return cs
|
||||
}
|
||||
|
||||
// Update triggers the stats collector to refresh it's statistics
|
||||
func (cs *ClusterStats) Update() {
|
||||
cs.old = cs.copy()
|
||||
|
||||
cs.reset()
|
||||
// Start looping through each host in the cluster, adding to the total
|
||||
for _, h := range cs.c.hosts {
|
||||
cs.Host.Count++
|
||||
cs.Host.Available++
|
||||
|
||||
cs.CPU.Sockets += h.HostInfo.Sockets
|
||||
cs.CPU.Cores += h.HostInfo.Cores
|
||||
cs.CPU.Threads += h.HostInfo.Threads
|
||||
|
||||
cs.Memory.Total += h.NodeMemory.Total
|
||||
cs.Memory.Free += h.NodeMemory.Free
|
||||
cs.Memory.Buffers += h.NodeMemory.Buffers
|
||||
cs.Memory.Cached += h.NodeMemory.Cached
|
||||
|
||||
// Storage Pool counting
|
||||
cs.Storage.Pools += uint32(len(h.StoragePoolList))
|
||||
countedSharedPools := map[string]struct{}{}
|
||||
// Loop through available storage pools
|
||||
for _, sp := range h.StoragePoolList {
|
||||
if _, ok := countedSharedPools[sp.Name]; ok {
|
||||
// Already counted this shared pool, move on
|
||||
continue
|
||||
}
|
||||
if sp.HAEnabled {
|
||||
countedSharedPools[sp.Name] = struct{}{}
|
||||
}
|
||||
if !sp.Active {
|
||||
cs.Storage.Inactive++
|
||||
continue
|
||||
}
|
||||
cs.Storage.Active++
|
||||
cs.Storage.Total += sp.Capacity
|
||||
cs.Storage.Used += sp.Allocation
|
||||
cs.Storage.Free += sp.Capacity - sp.Allocation
|
||||
// Volumes in the pool
|
||||
cs.Storage.Volumes.Total += uint32(len(sp.Volumes))
|
||||
for range sp.Volumes {
|
||||
cs.Storage.Volumes.Active++
|
||||
}
|
||||
}
|
||||
|
||||
// VM Count
|
||||
cs.VM.Count += uint32(len(h.VMList))
|
||||
for _, vm := range h.VMList {
|
||||
cs.CPU.Allocated += uint32(vm.VCPUs)
|
||||
cs.Memory.Allocated += uint64(vm.Memory)
|
||||
if vm.Active {
|
||||
cs.VM.Started++
|
||||
continue
|
||||
}
|
||||
cs.VM.Stopped++
|
||||
}
|
||||
|
||||
// Network count
|
||||
cs.Network.Count += uint32(len(h.NetworkList))
|
||||
for _, ni := range h.NetworkList {
|
||||
if ni.Active {
|
||||
cs.Network.Active++
|
||||
continue
|
||||
}
|
||||
cs.Network.Inactive++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Diff returns a map of all the field and how they changed
|
||||
func (cs *ClusterStats) Diff() StatDiff {
|
||||
return StatDiff{
|
||||
CPU: struct {
|
||||
Sockets int
|
||||
Cores int
|
||||
Threads int
|
||||
Allocated int
|
||||
}{
|
||||
Sockets: int(cs.CPU.Sockets) - int(cs.old.CPU.Sockets),
|
||||
Cores: int(cs.CPU.Cores) - int(cs.old.CPU.Cores),
|
||||
Threads: int(cs.CPU.Threads) - int(cs.old.CPU.Threads),
|
||||
Allocated: int(cs.CPU.Allocated) - int(cs.old.CPU.Allocated),
|
||||
},
|
||||
Memory: struct {
|
||||
Total int
|
||||
Free int
|
||||
Buffers int
|
||||
Cached int
|
||||
Allocated int
|
||||
}{
|
||||
Total: int(cs.old.Memory.Total - cs.Memory.Total),
|
||||
Free: int(cs.old.Memory.Free - cs.Memory.Free),
|
||||
Buffers: int(cs.old.Memory.Buffers - cs.Memory.Buffers),
|
||||
Cached: int(cs.old.Memory.Cached - cs.Memory.Cached),
|
||||
Allocated: int(cs.old.Memory.Allocated - cs.Memory.Allocated),
|
||||
},
|
||||
Storage: struct {
|
||||
Total int
|
||||
Used int
|
||||
Free int
|
||||
Active int
|
||||
Inactive int
|
||||
Pools int
|
||||
Volumes struct {
|
||||
Total int
|
||||
Active int
|
||||
Inactive int
|
||||
}
|
||||
}{
|
||||
Total: int(cs.old.Storage.Total - cs.Storage.Total),
|
||||
Used: int(cs.old.Storage.Used - cs.Storage.Used),
|
||||
Free: int(cs.old.Storage.Free - cs.Storage.Free),
|
||||
Active: int(cs.old.Storage.Active - cs.Storage.Active),
|
||||
Inactive: int(cs.old.Storage.Inactive - cs.Storage.Inactive),
|
||||
Pools: int(cs.old.Storage.Pools - cs.Storage.Pools),
|
||||
Volumes: struct {
|
||||
Total int
|
||||
Active int
|
||||
Inactive int
|
||||
}{
|
||||
Total: int(cs.old.Storage.Volumes.Total - cs.Storage.Volumes.Total),
|
||||
Active: int(cs.old.Storage.Volumes.Active - cs.Storage.Volumes.Active),
|
||||
Inactive: int(cs.old.Storage.Volumes.Inactive - cs.Storage.Volumes.Inactive),
|
||||
},
|
||||
},
|
||||
VM: struct {
|
||||
Count int
|
||||
Started int
|
||||
Stopped int
|
||||
}{
|
||||
Count: int(cs.old.VM.Count - cs.VM.Count),
|
||||
Started: int(cs.old.VM.Started - cs.VM.Started),
|
||||
Stopped: int(cs.old.VM.Stopped - cs.VM.Stopped),
|
||||
},
|
||||
Host: struct {
|
||||
Count int
|
||||
Available int
|
||||
}{
|
||||
Count: int(cs.old.Host.Count - cs.Host.Count),
|
||||
Available: int(cs.old.Host.Available - cs.Host.Available),
|
||||
},
|
||||
Network: struct {
|
||||
Count int
|
||||
Active int
|
||||
Inactive int
|
||||
}{
|
||||
Count: int(cs.old.Network.Count - cs.Network.Count),
|
||||
Active: int(cs.old.Network.Active - cs.Network.Active),
|
||||
Inactive: int(cs.old.Network.Inactive - cs.Network.Inactive),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// copy the clusterstats into a new clusterstatus object for comparison purposes
|
||||
func (cs *ClusterStats) copy() *ClusterStats {
|
||||
ncs := *cs
|
||||
return &ncs
|
||||
}
|
||||
|
||||
// reset all values to zero value
|
||||
func (cs *ClusterStats) reset() {
|
||||
cs.CPU.Sockets = 0
|
||||
cs.CPU.Cores = 0
|
||||
cs.CPU.Threads = 0
|
||||
cs.CPU.Allocated = 0
|
||||
|
||||
cs.Memory.Total = 0
|
||||
cs.Memory.Free = 0
|
||||
cs.Memory.Buffers = 0
|
||||
cs.Memory.Cached = 0
|
||||
|
||||
cs.Storage.Total = 0
|
||||
cs.Storage.Used = 0
|
||||
cs.Storage.Free = 0
|
||||
cs.Storage.Active = 0
|
||||
cs.Storage.Inactive = 0
|
||||
cs.Storage.Pools = 0
|
||||
|
||||
cs.VM.Count = 0
|
||||
cs.VM.Started = 0
|
||||
cs.VM.Stopped = 0
|
||||
|
||||
cs.Host.Count = 0
|
||||
cs.Host.Available = 0
|
||||
|
||||
cs.Network.Count = 0
|
||||
cs.Network.Active = 0
|
||||
cs.Network.Inactive = 0
|
||||
}
|
1
cluster/storagepool.go
Normal file
@ -0,0 +1 @@
|
||||
package cluster
|
1
cluster/vm.go
Normal file
@ -0,0 +1 @@
|
||||
package cluster
|
29
cmd/clustvirt.go
Normal file
@ -0,0 +1,29 @@
|
||||
// Package cmd contains the main application file for ClustVirt.
|
||||
// This command is used for everything, implementing the command pattern
|
||||
// with the help of spf13/cobra.
|
||||
// After building, the executable's first argument must be one of the valid
|
||||
// commands, such as `daemon`, `api`, `query`, `host`, etc. This pattern allows
|
||||
// for a simple way to access functionality, reducing the need to use the
|
||||
// REST api directly.
|
||||
package cmd
|
||||
|
||||
// Global Arguments:
|
||||
// All commands can use the following arguments:
|
||||
// --output none|json|xml: Set the output type, none being just plain log,
|
||||
// json outputing as a JSON document, and XML outputing as an XML document
|
||||
|
||||
// Commands planned:
|
||||
// config: actions associated with the configuration of clustvirt
|
||||
// set: set a configuration value
|
||||
// get: get the current value of a configuration options
|
||||
// list: list available configuration options
|
||||
// daemon: actions associated with running clustvirt in daemon mode
|
||||
// start: start the daemon
|
||||
// stop: safely stop the daemon
|
||||
// status: query the status of the daemon
|
||||
// api: query the REST api. There must a clustvirt daemon running for this to function
|
||||
// host: actions associated with a specific host in the cluster
|
||||
// vm: actions asociated with a specifc vm in the cluster
|
||||
// storagepool: actions associated with storage pools
|
||||
// auth: actions associated with authentication
|
||||
// network: actions to define networks
|
9
config/config.go
Normal file
@ -0,0 +1,9 @@
|
||||
// Package config used spf13/viper to handle configuration. Sane defaults are set
|
||||
// as default values, and a single host is set to `qemu:///system`, which is only
|
||||
// used if it is found. Not having a local instance will not panic or crash anything.
|
||||
//
|
||||
// Viper uses configuration set via defaults -> config file -> env variables ->
|
||||
// cli flags -> calls to Set (while running), so clustvirt uses the same method.
|
||||
package config
|
||||
|
||||
|
27
css/style.css
Normal file
@ -0,0 +1,27 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@tailwind variants;
|
||||
|
||||
@layer components {
|
||||
.notimpl {
|
||||
@apply italic font-light list-image-possible;
|
||||
}
|
||||
|
||||
div.infobox {
|
||||
@apply m-2 px-4 pt-2 flex-col shadow-md rounded-2xl border-8 border-double;
|
||||
@apply divide-solid divide-y-2;
|
||||
}
|
||||
|
||||
span>a::after {
|
||||
@apply content-link w-3 h-3 inline-block invert;
|
||||
}
|
||||
|
||||
span>a {
|
||||
@apply text-base text-uiblue-400;
|
||||
}
|
||||
|
||||
span>a:visited {
|
||||
@apply text-base text-uiorange-400;
|
||||
}
|
||||
}
|
14
go.mod
@ -2,9 +2,17 @@ module git.staur.ca/stobbsm/clustvirt
|
||||
|
||||
go 1.22.1
|
||||
|
||||
require libvirt.org/go/libvirt v1.10001.0
|
||||
require (
|
||||
github.com/a-h/templ v0.2.598
|
||||
github.com/go-chi/chi/v5 v5.0.12
|
||||
github.com/wcharczuk/go-chart/v2 v2.1.1
|
||||
gopkg.in/ffmt.v1 v1.5.6
|
||||
libvirt.org/go/libvirt v1.10001.0
|
||||
libvirt.org/go/libvirtxml v1.10001.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/go-chi/chi/v5 v5.0.12 // indirect
|
||||
gopkg.in/ffmt.v1 v1.5.6 // indirect
|
||||
github.com/blend/go-sdk v1.20220411.3 // indirect
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
|
||||
golang.org/x/image v0.11.0 // indirect
|
||||
)
|
||||
|
45
go.sum
@ -1,6 +1,51 @@
|
||||
github.com/a-h/templ v0.2.598 h1:6jMIHv6wQZvdPxTuv87erW4RqN/FPU0wk7ZHN5wVuuo=
|
||||
github.com/a-h/templ v0.2.598/go.mod h1:SA7mtYwVEajbIXFRh3vKdYm/4FYyLQAtPH1+KxzGPA8=
|
||||
github.com/blend/go-sdk v1.20220411.3 h1:GFV4/FQX5UzXLPwWV03gP811pj7B8J2sbuq+GJQofXc=
|
||||
github.com/blend/go-sdk v1.20220411.3/go.mod h1:7lnH8fTi6U4i1fArEXRyOIY2E1X4MALg09qsQqY1+ak=
|
||||
github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s=
|
||||
github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/wcharczuk/go-chart/v2 v2.1.1 h1:2u7na789qiD5WzccZsFz4MJWOJP72G+2kUuJoSNqWnE=
|
||||
github.com/wcharczuk/go-chart/v2 v2.1.1/go.mod h1:CyCAUt2oqvfhCl6Q5ZvAZwItgpQKZOkCJGb+VGv6l14=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/image v0.11.0 h1:ds2RoQvBvYTiJkwpSFDwCcDFNX7DqjL2WsUgTNk0Ooo=
|
||||
golang.org/x/image v0.11.0/go.mod h1:bglhjqbqVuEb9e9+eNR45Jfu7D+T4Qan+NhQk8Ck2P8=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/ffmt.v1 v1.5.6 h1:4Bu3riZp5sAIXW2T/18JM9BkwJLodurXFR0f7PXp+cw=
|
||||
gopkg.in/ffmt.v1 v1.5.6/go.mod h1:LssvGOZFiBGoBcobkTqnyh+uN1VzIRoibW+c0JI/Ha4=
|
||||
libvirt.org/go/libvirt v1.10001.0 h1:lEVDNE7xfzmZXiDEGIS8NvJSuaz11OjRXw+ufbQEtPY=
|
||||
libvirt.org/go/libvirt v1.10001.0/go.mod h1:1WiFE8EjZfq+FCVog+rvr1yatKbKZ9FaFMZgEqxEJqQ=
|
||||
libvirt.org/go/libvirtxml v1.10001.0 h1:r9WBs24r3mxIG3/hAMRRwDMy4ZaPHmhHjw72o/ceXic=
|
||||
libvirt.org/go/libvirtxml v1.10001.0/go.mod h1:7Oq2BLDstLr/XtoQD8Fr3mfDNrzlI3utYKySXF2xkng=
|
||||
|
38
lib/host/charts.go
Normal file
@ -0,0 +1,38 @@
|
||||
package host
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/wcharczuk/go-chart/v2"
|
||||
)
|
||||
|
||||
// This file contains utilities to create charts based on the different data
|
||||
// When a chart is rendered, it can return either an SVG or PNG. SVG is preferrable.
|
||||
|
||||
func (h *Host) ChartMemory() string {
|
||||
h.getNodeInfo()
|
||||
log.Println("Generating Chart")
|
||||
memFree := float64(h.NodeMemory.Free)
|
||||
memCached := float64(h.NodeMemory.Cached)
|
||||
memBuffer := float64(h.NodeMemory.Buffers)
|
||||
memTotal := float64(h.NodeMemory.Total)
|
||||
|
||||
c := chart.PieChart{
|
||||
Title: fmt.Sprintf("Memory Info %s", h.SystemHostName),
|
||||
Width: 256,
|
||||
Height: 256,
|
||||
Values: []chart.Value{
|
||||
{Value: memTotal - memFree, Label: fmt.Sprintf("%.2f%% Free", memFree/memTotal*100)},
|
||||
{Value: memTotal - memCached, Label: fmt.Sprintf("%.2f%% Cached", memCached/memTotal*100)},
|
||||
{Value: memTotal - memBuffer, Label: fmt.Sprintf("%.2f%% Buffers", memBuffer/memTotal*100)},
|
||||
},
|
||||
}
|
||||
sb := new(strings.Builder)
|
||||
log.Println("Rendering chart")
|
||||
if err := c.Render(chart.SVG, sb); err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
return sb.String()
|
||||
}
|
@ -16,6 +16,7 @@ import (
|
||||
"git.staur.ca/stobbsm/clustvirt/lib/storagevol"
|
||||
"git.staur.ca/stobbsm/clustvirt/util"
|
||||
"libvirt.org/go/libvirt"
|
||||
"libvirt.org/go/libvirtxml"
|
||||
)
|
||||
|
||||
// Host holds information and acts as a connection handle for a Host
|
||||
@ -80,6 +81,9 @@ type VMInfo struct {
|
||||
ID uint
|
||||
UUID []byte
|
||||
XML string
|
||||
Active bool
|
||||
VCPUs uint
|
||||
Memory uint
|
||||
// States are the current states active on the host
|
||||
States []guest.VMState
|
||||
}
|
||||
@ -105,6 +109,7 @@ type StoragePoolInfo struct {
|
||||
Capacity uint64
|
||||
Allocation uint64
|
||||
Available uint64
|
||||
IsNet bool
|
||||
// HAEnabled indicates if the storage pool has High Availability
|
||||
HAEnabled bool
|
||||
// Volumes defined in the storage pool
|
||||
@ -113,13 +118,13 @@ type StoragePoolInfo struct {
|
||||
|
||||
// VolumeInfo holds basic information about Volumes available in storage pools
|
||||
type VolumeInfo struct {
|
||||
Name string
|
||||
Key string
|
||||
Path string
|
||||
Type string
|
||||
Capacity uint64
|
||||
Name string
|
||||
Key string
|
||||
Path string
|
||||
Type string
|
||||
Capacity uint64
|
||||
Allocation uint64
|
||||
XML string
|
||||
XML string
|
||||
}
|
||||
|
||||
// NetIfInfo holds basic information about available network interfaces (not their connections, the devices themselves)
|
||||
@ -134,6 +139,7 @@ type NetworkInfo struct {
|
||||
Name string
|
||||
UUID []byte
|
||||
XML string
|
||||
Active bool
|
||||
// NetIf is the network interface this connection is applied to
|
||||
NetIf NetIfInfo
|
||||
}
|
||||
@ -228,29 +234,31 @@ func (h *Host) Close() error {
|
||||
// private methods that load the different informational parts
|
||||
|
||||
func (h *Host) getInfo() {
|
||||
var wg sync.WaitGroup
|
||||
var wg = new(sync.WaitGroup)
|
||||
|
||||
infoFuncs := []func(*sync.WaitGroup){
|
||||
infoFuncs := []func(){
|
||||
h.getDevicesInfo,
|
||||
h.getDomainInfo,
|
||||
// h.getDomainInfo,
|
||||
h.getIfaceInfo,
|
||||
h.getNetsInfo,
|
||||
h.getNodeInfo,
|
||||
h.getSEVInfo,
|
||||
h.getSecretsInfo,
|
||||
h.getStoragePools,
|
||||
// h.getSEVInfo,
|
||||
// h.getSecretsInfo,
|
||||
// h.getStoragePools,
|
||||
}
|
||||
|
||||
for _, f := range infoFuncs {
|
||||
wg.Add(1)
|
||||
f(&wg)
|
||||
go func(wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
f()
|
||||
}(wg)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func (h *Host) getStoragePools(wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
func (h *Host) getStoragePools() {
|
||||
spools, err := h.conn.ListAllStoragePools(0)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
@ -285,6 +293,19 @@ func (h *Host) getStoragePools(wg *sync.WaitGroup) {
|
||||
h.StoragePoolList[i].Allocation = spInfo.Allocation
|
||||
h.StoragePoolList[i].Available = spInfo.Available
|
||||
|
||||
spoolXML := &libvirtxml.StoragePool{}
|
||||
if err = spoolXML.Unmarshal(h.StoragePoolList[i].XML); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
h.StoragePoolList[i].Type = spoolXML.Type
|
||||
for _, t := range storagepool.NetTypes {
|
||||
if h.StoragePoolList[i].Type == t {
|
||||
h.StoragePoolList[i].IsNet = true
|
||||
h.StoragePoolList[i].HAEnabled = true
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
svols, err := s.ListAllStorageVolumes(0)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
@ -295,7 +316,7 @@ func (h *Host) getStoragePools(wg *sync.WaitGroup) {
|
||||
if h.StoragePoolList[i].Volumes[j].Name, err = sv.GetName(); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
if h.StoragePoolList[i].Volumes[j].Key, err = sv.GetKey(); err != nil {
|
||||
if h.StoragePoolList[i].Volumes[j].Key, err = sv.GetKey(); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
if h.StoragePoolList[i].Volumes[j].Path, err = sv.GetPath(); err != nil {
|
||||
@ -321,8 +342,7 @@ func (h *Host) getStoragePools(wg *sync.WaitGroup) {
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Host) getSecretsInfo(wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
func (h *Host) getSecretsInfo() {
|
||||
nsecrets, err := h.conn.ListAllSecrets(0)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
@ -344,8 +364,7 @@ func (h *Host) getSecretsInfo(wg *sync.WaitGroup) {
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Host) getNodeInfo(wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
func (h *Host) getNodeInfo() {
|
||||
var err error
|
||||
h.AvailableCPUTypes, err = h.conn.GetCPUModelNames("x86_64", 0)
|
||||
if err != nil {
|
||||
@ -413,8 +432,7 @@ func (h *Host) getNodeInfo(wg *sync.WaitGroup) {
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Host) getSEVInfo(wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
func (h *Host) getSEVInfo() {
|
||||
// getSEVInfo
|
||||
h.HostSEVInfo.SEVEnabled = true
|
||||
ns, err := h.conn.GetSEVInfo(0)
|
||||
@ -442,8 +460,7 @@ func (h *Host) getSEVInfo(wg *sync.WaitGroup) {
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Host) getDomainInfo(wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
func (h *Host) getDomainInfo() {
|
||||
// getDomainInfo
|
||||
doms, err := h.conn.ListAllDomains(0)
|
||||
if err != nil {
|
||||
@ -465,14 +482,22 @@ func (h *Host) getDomainInfo(wg *sync.WaitGroup) {
|
||||
if h.VMList[i].XML, err = d.GetXMLDesc(0); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
vmXML := &libvirtxml.Domain{}
|
||||
if err = vmXML.Unmarshal(h.VMList[i].XML); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
h.VMList[i].VCPUs = vmXML.VCPU.Value
|
||||
h.VMList[i].Memory = vmXML.CurrentMemory.Value
|
||||
if h.VMList[i].Active, err = d.IsActive(); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
|
||||
d.Free()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Host) getIfaceInfo(wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
func (h *Host) getIfaceInfo() {
|
||||
// getIfaceInfo
|
||||
ifaces, err := h.conn.ListInterfaces()
|
||||
if err != nil {
|
||||
@ -498,8 +523,7 @@ func (h *Host) getIfaceInfo(wg *sync.WaitGroup) {
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Host) getNetsInfo(wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
func (h *Host) getNetsInfo() {
|
||||
// getNetsInfo
|
||||
nets, err := h.conn.ListNetworks()
|
||||
if err != nil {
|
||||
@ -521,14 +545,16 @@ func (h *Host) getNetsInfo(wg *sync.WaitGroup) {
|
||||
if h.NetworkList[i].XML, err = net.GetXMLDesc(0); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
if h.NetworkList[i].Active, err = net.IsActive(); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
|
||||
net.Free()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Host) getDevicesInfo(wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
func (h *Host) getDevicesInfo() {
|
||||
ndevs, err := h.conn.ListAllNodeDevices(0)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
|
@ -1 +1,6 @@
|
||||
// Package network defines varialbes and methods to control libvirtd
|
||||
// networks. Networks Can be defined cluster wide, and mapped to
|
||||
// host interfaces this way.
|
||||
package network
|
||||
|
||||
type Network struct{}
|
||||
|
@ -9,3 +9,38 @@ var StoragePoolStateMap = map[libvirt.StoragePoolState]string{
|
||||
libvirt.STORAGE_POOL_DEGRADED: "degraded",
|
||||
libvirt.STORAGE_POOL_INACCESSIBLE: "inaccessible",
|
||||
}
|
||||
|
||||
var Types = []string{
|
||||
"dir",
|
||||
"fs",
|
||||
"netfs",
|
||||
"disk",
|
||||
"iscsi",
|
||||
"logical",
|
||||
"scsi",
|
||||
"mpath",
|
||||
"rbd",
|
||||
"sheepdog",
|
||||
"gluster",
|
||||
"zfs",
|
||||
"iscsi-direct",
|
||||
}
|
||||
|
||||
var LocalTypes = []string{
|
||||
"dir",
|
||||
"fs",
|
||||
"disk",
|
||||
"logical",
|
||||
"scsi",
|
||||
"zfs",
|
||||
}
|
||||
|
||||
var NetTypes = []string{
|
||||
"netfs",
|
||||
"iscsi",
|
||||
"mpath",
|
||||
"rbd",
|
||||
"sheepdog",
|
||||
"gluster",
|
||||
"iscsi-direct",
|
||||
}
|
||||
|
78
main.go
@ -1,10 +1,17 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"git.staur.ca/stobbsm/clustvirt/cluster"
|
||||
"git.staur.ca/stobbsm/clustvirt/lib/host"
|
||||
"git.staur.ca/stobbsm/clustvirt/view"
|
||||
"git.staur.ca/stobbsm/clustvirt/view/components"
|
||||
"git.staur.ca/stobbsm/clustvirt/view/static"
|
||||
"github.com/a-h/templ"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
)
|
||||
@ -13,42 +20,63 @@ const DEBUG bool = true
|
||||
|
||||
func main() {
|
||||
log.Println("Starting clustvirt, the libvirt cluster manager")
|
||||
clst := cluster.New().
|
||||
AddHost("earth.staur.ca").
|
||||
AddHost("venus.staur.ca").
|
||||
AddHost("mars.staur.ca").
|
||||
Build()
|
||||
|
||||
//venus, err := host.ConnectHost(host.URI_QEMU_SSH_SYSTEM, "venus.staur.ca")
|
||||
//if err != nil {
|
||||
// log.Fatal(err)
|
||||
//}
|
||||
//defer venus.Close()
|
||||
//lm, err := venus.GetGuestByName("logan-minecraft")
|
||||
//if err != nil {
|
||||
// log.Fatal(err)
|
||||
//}
|
||||
//defer lm.Close()
|
||||
|
||||
initStats := cluster.InitStats(clst)
|
||||
// Start webserver and serve homepage
|
||||
|
||||
defaultNavBar := []components.NavItem{
|
||||
{Name: "Cluster", Href: "/"},
|
||||
{Name: "Configuration", Href: "/config"},
|
||||
{Name: "About", Href: "/about"},
|
||||
}
|
||||
fs := http.StripPrefix("/static/", http.FileServer(http.Dir("public")))
|
||||
|
||||
r := chi.NewRouter()
|
||||
r.Use(middleware.Logger)
|
||||
if DEBUG {
|
||||
r.Use(middleware.NoCache)
|
||||
}
|
||||
r.Get("/static/*", func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
if DEBUG {
|
||||
w.Header().Add("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||
w.Header().Add("Pragma", "no-cache")
|
||||
w.Header().Add("Expire", "0")
|
||||
}
|
||||
fs.ServeHTTP(w, r)
|
||||
})
|
||||
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
if DEBUG {
|
||||
w.Header().Add("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||
w.Header().Add("Pragma", "no-cache")
|
||||
w.Header().Add("Expire", "0")
|
||||
}
|
||||
w.Header().Add("Content", "text/html")
|
||||
log.Println(view.ViewHome.Render(w, nil))
|
||||
r.Get("/", templ.Handler(view.HostMain(defaultNavBar)).ServeHTTP)
|
||||
r.Get("/cluster", func(w http.ResponseWriter, r *http.Request) {
|
||||
initStats.Update()
|
||||
log.Println(
|
||||
"Rendering clusterstats",
|
||||
view.ClusterInfo(
|
||||
initStats,
|
||||
initStats.Diff(),
|
||||
defaultNavBar,
|
||||
).Render(context.Background(), w))
|
||||
})
|
||||
r.Get("/about", templ.Handler(static.Home()).ServeHTTP)
|
||||
|
||||
r.Route("/htmx", func(r chi.Router) {
|
||||
r.Get("/host/{hostname}", func(w http.ResponseWriter, r *http.Request) {
|
||||
rhost, err := host.ConnectHost(host.URI_QEMU_SSH_SYSTEM, chi.URLParam(r, "hostname"))
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("error while getting host: %s", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer rhost.Close()
|
||||
log.Println("Rendering HostInfo", view.HostInfo(rhost).Render(context.Background(), w))
|
||||
})
|
||||
r.Get("/host/{hostname}/stats", func(w http.ResponseWriter, r *http.Request) {
|
||||
rhost, err := host.ConnectHost(host.URI_QEMU_SSH_SYSTEM, chi.URLParam(r, "hostname"))
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("error while getting host: %s", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer rhost.Close()
|
||||
log.Println("Rendering stats", view.SysInfo(rhost).Render(context.Background(), w))
|
||||
})
|
||||
})
|
||||
|
||||
log.Println(http.ListenAndServe(":3000", r))
|
||||
|
21
assets/package-lock.json → package-lock.json
generated
@ -1,12 +1,11 @@
|
||||
{
|
||||
"name": "assets",
|
||||
"name": "clustvirt",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"devDependencies": {
|
||||
"@tailwindcss/forms": "^0.5.7",
|
||||
"tailwind-dracula": "^1.1.0",
|
||||
"tailwindcss": "^3.4.1"
|
||||
}
|
||||
},
|
||||
@ -200,12 +199,15 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/binary-extensions": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
|
||||
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
|
||||
"integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
@ -1195,15 +1197,6 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/tailwind-dracula": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/tailwind-dracula/-/tailwind-dracula-1.1.0.tgz",
|
||||
"integrity": "sha512-LjU1df6WGS5eUwozdPtZ7jARojHYTIe6y1Gj5v2QNaNnMnLm7ipPtlIEGlNXXraWjVnWeRbLZl4vvr48mMFGxw==",
|
||||
"dev": true,
|
||||
"peerDependencies": {
|
||||
"tailwindcss": "2.x || 3.x"
|
||||
}
|
||||
},
|
||||
"node_modules/tailwindcss": {
|
||||
"version": "3.4.1",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.1.tgz",
|
@ -1,7 +1,6 @@
|
||||
{
|
||||
"devDependencies": {
|
||||
"@tailwindcss/forms": "^0.5.7",
|
||||
"tailwind-dracula": "^1.1.0",
|
||||
"tailwindcss": "^3.4.1"
|
||||
}
|
||||
}
|
1
public/chart.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="256" height="256">\n<path d="M 0 0 L 256 0 L 256 256 L 0 256 L 0 0" style="stroke-width:0;stroke:rgba(255,255,255,1.0);fill:rgba(255,255,255,1.0)"/><path d="M 5 5 L 251 5 L 251 251 L 5 251 L 5 5" style="stroke-width:0;stroke:rgba(255,255,255,1.0);fill:rgba(255,255,255,1.0)"/><circle cx="128" cy="128" r="123" style="stroke-width:5;stroke:rgba(255,255,255,1.0);fill:rgba(106,195,203,1.0)"/><text x="33" y="134" style="stroke-width:0;stroke:none;fill:rgba(51,51,51,1.0);font-size:12.8px;font-family:'Roboto Medium',sans-serif">Free</text></svg>
|
After Width: | Height: | Size: 634 B |
219458
public/css/style.css
@ -38,7 +38,7 @@
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="m 8.3076888,12.923076 2.7692302,2.769231 4.615384,-6.4615418 m 7.384614,2.7692328 a 11.076921,11.076928 0 1 1 -22.15384198,0 11.076921,11.076928 0 0 1 22.15384198,0 z"
|
||||
d="m 6.9230763,14.767385 2.3076926,2.307694 3.8461551,-5.38462 m 6.153846,2.307695 a 9.2307704,9.2307764 0 1 1 -18.46154043,0 9.2307704,9.2307764 0 0 1 18.46154043,0 z"
|
||||
id="path1"
|
||||
style="stroke-width:1.84615;stroke:#d6d6d6;stroke-opacity:1" />
|
||||
style="stroke:#d6d6d6;stroke-width:1.53846;stroke-opacity:1" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
@ -29,16 +29,16 @@
|
||||
inkscape:zoom="9.8333333"
|
||||
inkscape:cx="9.7627119"
|
||||
inkscape:cy="9.7627119"
|
||||
inkscape:window-width="1312"
|
||||
inkscape:window-height="449"
|
||||
inkscape:window-width="1440"
|
||||
inkscape:window-height="847"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="25"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg1" />
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="m 11.076927,11.076927 0.05046,-0.02462 a 0.92307691,0.9230774 0 0 1 1.308308,1.048616 l -0.871384,3.490464 a 0.92307691,0.9230774 0 0 0 1.308308,1.049846 l 0.05046,-0.02585 m 10.153842,-4.615384 a 11.076923,11.076929 0 1 1 -22.15384598,0 11.076923,11.076929 0 0 1 22.15384598,0 z M 12.000004,7.3846176 h 0.0098 v 0.00985 h -0.0098 z"
|
||||
d="m 9.2307739,13.229418 0.04205,-0.02052 a 0.76923083,0.76923136 0 0 1 1.0902571,0.873847 l -0.7261531,2.908721 a 0.76923083,0.76923136 0 0 0 1.0902561,0.874872 l 0.04205,-0.02154 m 8.461536,-3.846154 a 9.23077,9.2307764 0 1 1 -18.46153989,0 9.23077,9.2307764 0 0 1 18.46153989,0 z m -9.230765,-3.846151 h 0.0082 v 0.0082 h -0.0082 z"
|
||||
id="path1"
|
||||
style="stroke:#d6d6d6;stroke-width:1.84615;stroke-opacity:1" />
|
||||
style="stroke:#d6d6d6;stroke-width:1.53846;stroke-opacity:1" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
@ -38,7 +38,7 @@
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M 19.833912,19.783417 A 11.082911,11.052709 0 0 0 4.1603098,4.1525257 M 19.833912,19.783417 A 11.082911,11.052709 0 0 1 4.1603098,4.1525257 M 19.833912,19.783417 4.1603098,4.1525257"
|
||||
d="M 16.532287,20.52382 A 9.23747,9.2323036 0 0 0 3.4685327,7.4673718 M 16.532287,20.52382 A 9.23747,9.2323036 0 0 1 3.4685327,7.4673718 M 16.532287,20.52382 3.4685327,7.4673718"
|
||||
id="path1"
|
||||
style="stroke:#d6d6d6;stroke-width:1.84462;stroke-opacity:1" />
|
||||
style="stroke:#d6d6d6;stroke-width:1.53913;stroke-opacity:1" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
@ -38,7 +38,7 @@
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M 15.692306,11.999999 H 8.3076904 m 14.7692306,0 a 11.076923,11.076929 0 1 1 -22.15384598,0 11.076923,11.076929 0 0 1 22.15384598,0 z"
|
||||
d="M 13.076923,13.99792 H 6.9230762 m 12.3076928,0 a 9.23077,9.2307764 0 1 1 -18.46153955,0 9.23077,9.2307764 0 0 1 18.46153955,0 z"
|
||||
id="path1"
|
||||
style="stroke:#d6d6d6;stroke-width:1.84615;stroke-opacity:1" />
|
||||
style="stroke:#d6d6d6;stroke-width:1.53846;stroke-opacity:1" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
52
public/images/bars.svg
Normal file
@ -0,0 +1,52 @@
|
||||
<svg width="135" height="140" viewBox="0 0 135 140" xmlns="http://www.w3.org/2000/svg" fill="#fff">
|
||||
<rect y="10" width="15" height="120" rx="6">
|
||||
<animate attributeName="height"
|
||||
begin="0.5s" dur="1s"
|
||||
values="120;110;100;90;80;70;60;50;40;140;120" calcMode="linear"
|
||||
repeatCount="indefinite" />
|
||||
<animate attributeName="y"
|
||||
begin="0.5s" dur="1s"
|
||||
values="10;15;20;25;30;35;40;45;50;0;10" calcMode="linear"
|
||||
repeatCount="indefinite" />
|
||||
</rect>
|
||||
<rect x="30" y="10" width="15" height="120" rx="6">
|
||||
<animate attributeName="height"
|
||||
begin="0.25s" dur="1s"
|
||||
values="120;110;100;90;80;70;60;50;40;140;120" calcMode="linear"
|
||||
repeatCount="indefinite" />
|
||||
<animate attributeName="y"
|
||||
begin="0.25s" dur="1s"
|
||||
values="10;15;20;25;30;35;40;45;50;0;10" calcMode="linear"
|
||||
repeatCount="indefinite" />
|
||||
</rect>
|
||||
<rect x="60" width="15" height="140" rx="6">
|
||||
<animate attributeName="height"
|
||||
begin="0s" dur="1s"
|
||||
values="120;110;100;90;80;70;60;50;40;140;120" calcMode="linear"
|
||||
repeatCount="indefinite" />
|
||||
<animate attributeName="y"
|
||||
begin="0s" dur="1s"
|
||||
values="10;15;20;25;30;35;40;45;50;0;10" calcMode="linear"
|
||||
repeatCount="indefinite" />
|
||||
</rect>
|
||||
<rect x="90" y="10" width="15" height="120" rx="6">
|
||||
<animate attributeName="height"
|
||||
begin="0.25s" dur="1s"
|
||||
values="120;110;100;90;80;70;60;50;40;140;120" calcMode="linear"
|
||||
repeatCount="indefinite" />
|
||||
<animate attributeName="y"
|
||||
begin="0.25s" dur="1s"
|
||||
values="10;15;20;25;30;35;40;45;50;0;10" calcMode="linear"
|
||||
repeatCount="indefinite" />
|
||||
</rect>
|
||||
<rect x="120" y="10" width="15" height="120" rx="6">
|
||||
<animate attributeName="height"
|
||||
begin="0.5s" dur="1s"
|
||||
values="120;110;100;90;80;70;60;50;40;140;120" calcMode="linear"
|
||||
repeatCount="indefinite" />
|
||||
<animate attributeName="y"
|
||||
begin="0.5s" dur="1s"
|
||||
values="10;15;20;25;30;35;40;45;50;0;10" calcMode="linear"
|
||||
repeatCount="indefinite" />
|
||||
</rect>
|
||||
</svg>
|
After Width: | Height: | Size: 2.3 KiB |
56
public/images/grid.svg
Normal file
@ -0,0 +1,56 @@
|
||||
<svg width="105" height="105" viewBox="0 0 105 105" xmlns="http://www.w3.org/2000/svg" fill="#fff">
|
||||
<circle cx="12.5" cy="12.5" r="12.5">
|
||||
<animate attributeName="fill-opacity"
|
||||
begin="0s" dur="1s"
|
||||
values="1;.2;1" calcMode="linear"
|
||||
repeatCount="indefinite" />
|
||||
</circle>
|
||||
<circle cx="12.5" cy="52.5" r="12.5" fill-opacity=".5">
|
||||
<animate attributeName="fill-opacity"
|
||||
begin="100ms" dur="1s"
|
||||
values="1;.2;1" calcMode="linear"
|
||||
repeatCount="indefinite" />
|
||||
</circle>
|
||||
<circle cx="52.5" cy="12.5" r="12.5">
|
||||
<animate attributeName="fill-opacity"
|
||||
begin="300ms" dur="1s"
|
||||
values="1;.2;1" calcMode="linear"
|
||||
repeatCount="indefinite" />
|
||||
</circle>
|
||||
<circle cx="52.5" cy="52.5" r="12.5">
|
||||
<animate attributeName="fill-opacity"
|
||||
begin="600ms" dur="1s"
|
||||
values="1;.2;1" calcMode="linear"
|
||||
repeatCount="indefinite" />
|
||||
</circle>
|
||||
<circle cx="92.5" cy="12.5" r="12.5">
|
||||
<animate attributeName="fill-opacity"
|
||||
begin="800ms" dur="1s"
|
||||
values="1;.2;1" calcMode="linear"
|
||||
repeatCount="indefinite" />
|
||||
</circle>
|
||||
<circle cx="92.5" cy="52.5" r="12.5">
|
||||
<animate attributeName="fill-opacity"
|
||||
begin="400ms" dur="1s"
|
||||
values="1;.2;1" calcMode="linear"
|
||||
repeatCount="indefinite" />
|
||||
</circle>
|
||||
<circle cx="12.5" cy="92.5" r="12.5">
|
||||
<animate attributeName="fill-opacity"
|
||||
begin="700ms" dur="1s"
|
||||
values="1;.2;1" calcMode="linear"
|
||||
repeatCount="indefinite" />
|
||||
</circle>
|
||||
<circle cx="52.5" cy="92.5" r="12.5">
|
||||
<animate attributeName="fill-opacity"
|
||||
begin="500ms" dur="1s"
|
||||
values="1;.2;1" calcMode="linear"
|
||||
repeatCount="indefinite" />
|
||||
</circle>
|
||||
<circle cx="92.5" cy="92.5" r="12.5">
|
||||
<animate attributeName="fill-opacity"
|
||||
begin="200ms" dur="1s"
|
||||
values="1;.2;1" calcMode="linear"
|
||||
repeatCount="indefinite" />
|
||||
</circle>
|
||||
</svg>
|
After Width: | Height: | Size: 2.0 KiB |
BIN
public/images/home/logo-banner-dark-800.png
(Stored with Git LFS)
Normal file
139
tailwind.config.js
Normal file
@ -0,0 +1,139 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: ["**/*.templ", "**/*.go"],
|
||||
safelist: [
|
||||
{
|
||||
pattern: /list-image-(accepted|informational|never|possible)/,
|
||||
},
|
||||
{
|
||||
pattern: /[a-z]+-ui(blue|cyan|green|grey|orange|pink|purple|red|yellow)-[0-9]+/
|
||||
},
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
listStyleImage: {
|
||||
never: 'url("/static/icons/list-never.svg")',
|
||||
accepted: 'url("/static/icons/list-accepted.svg")',
|
||||
possible: 'url("/static/icons/list-possible.svg")',
|
||||
informational: 'url("/static/icons/list-informational.svg")',
|
||||
},
|
||||
content: {
|
||||
link: 'url("/static/icons/link.svg")',
|
||||
},
|
||||
colors: {
|
||||
'uiblue': {
|
||||
50: '#eef3fb',
|
||||
100: '#dde7f8',
|
||||
200: '#bbcff1',
|
||||
300: '#99b6e9',
|
||||
400: '#779ee2',
|
||||
500: '#5586db',
|
||||
600: '#446baf',
|
||||
700: '#335083',
|
||||
800: '#223658',
|
||||
900: '#111b2c',
|
||||
},
|
||||
'uicyan': {
|
||||
50: '#f2fefd',
|
||||
100: '#e5fdfc',
|
||||
200: '#cbfaf9',
|
||||
300: '#b2f8f5',
|
||||
400: '#98f5f2',
|
||||
500: '#7ef3ef',
|
||||
600: '#65c2bf',
|
||||
700: '#4c928f',
|
||||
800: '#326160',
|
||||
900: '#193130',
|
||||
},
|
||||
'uigreen': {
|
||||
50: '#f5f9f5',
|
||||
100: '#ebf2ec',
|
||||
200: '#d8e5d8',
|
||||
300: '#c4d9c5',
|
||||
400: '#b1ccb1',
|
||||
500: '#9dbf9e',
|
||||
600: '#7e997e',
|
||||
700: '#5e735f',
|
||||
800: '#3f4c3f',
|
||||
900: '#1f2620',
|
||||
},
|
||||
'uigrey': {
|
||||
50: '#f5f5f5',
|
||||
100: '#eaeaea',
|
||||
200: '#d6d6d6',
|
||||
300: '#c1c1c1',
|
||||
400: '#adadad',
|
||||
500: '#989898',
|
||||
600: '#7a7a7a',
|
||||
700: '#5b5b5b',
|
||||
800: '#3d3d3d',
|
||||
900: '#1e1e1e',
|
||||
},
|
||||
'uiorange': {
|
||||
50: '#f9f3ef',
|
||||
100: '#f2e8df',
|
||||
200: '#e5d0bf',
|
||||
300: '#d8b9a0',
|
||||
400: '#cba180',
|
||||
500: '#be8a60',
|
||||
600: '#986e4d',
|
||||
700: '#72533a',
|
||||
800: '#4c3726',
|
||||
900: '#261c13',
|
||||
},
|
||||
'uipink': {
|
||||
50: '#faf0f9',
|
||||
100: '#f5e1f3',
|
||||
200: '#ebc3e7',
|
||||
300: '#e0a5d8',
|
||||
400: '#d687cf',
|
||||
500: '#cc69c3',
|
||||
600: '#a3549c',
|
||||
700: '#7a3f75',
|
||||
800: '#522a4e',
|
||||
900: '#291527',
|
||||
},
|
||||
'uipurple': {
|
||||
50: '#fceefc',
|
||||
100: '#f8dcf8',
|
||||
200: '#f1b9f2',
|
||||
300: '#ea97eb',
|
||||
400: '#e374e5',
|
||||
500: '#dc51de',
|
||||
600: '#b041b2',
|
||||
700: '#843185',
|
||||
800: '#582059',
|
||||
900: '#2c102c',
|
||||
},
|
||||
'uired': {
|
||||
50: '#fbe9ef',
|
||||
100: '#f7d2de',
|
||||
200: '#efa5bd',
|
||||
300: '#e8789d',
|
||||
400: '#e04b7c',
|
||||
500: '#d81e5b',
|
||||
600: '#ad1849',
|
||||
700: '#821237',
|
||||
800: '#560c24',
|
||||
900: '#2b0612',
|
||||
},
|
||||
'uiyellow': {
|
||||
50: '#fffff5',
|
||||
100: '#ffffea',
|
||||
200: '#fffed6',
|
||||
300: '#fffec1',
|
||||
400: '#fffdad',
|
||||
500: '#fffd98',
|
||||
600: '#ccca7a',
|
||||
700: '#99985b',
|
||||
800: '#66653d',
|
||||
900: '#33331e',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
require('@tailwindcss/forms'),
|
||||
],
|
||||
}
|
||||
|
@ -1,7 +0,0 @@
|
||||
{{ define "footer" }}
|
||||
<div class="flex gap-4 md:gap-6 sm:gap-8 divide divide-solid">
|
||||
<div id="footer_left" class="flex-auto basis-1/4 md:basis-1/3 p-2">Left</div>
|
||||
<div id="footer_middle" class="flex-auto basis-1/2 md:basis-1/3 p-2">Middle</div>
|
||||
<div id="footer_right" class="flex-auto basis-1/4 md:basis-1/3 p-2">Right</div>
|
||||
</div>
|
||||
{{ end }}
|
@ -1,10 +0,0 @@
|
||||
{{ define "header" }}
|
||||
<h1 class="text-2xl font-bold">Clustvirt</h1>
|
||||
<h2 class="text-sm font-thin italic">Libvirtd simplified and clustered</h2>
|
||||
<nav>
|
||||
<ul>
|
||||
<li>Server 1</li>
|
||||
<li>Server 2</li>
|
||||
</ul>
|
||||
</nav>
|
||||
{{ end }}
|
@ -1,22 +0,0 @@
|
||||
{{ define "_index" }}
|
||||
<!DOCTYPE html>
|
||||
<html class="text-nosferatu-100 bg-nosferatu-900">
|
||||
<head>
|
||||
<title>Clustvirt</title>
|
||||
<link href="/static/css/style.css" type="text/css" rel="stylesheet"/>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
{{ template "header" }}
|
||||
</header>
|
||||
|
||||
<div id="content" name="content">
|
||||
{{ template "content" . }}
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
{{ template "footer" }}
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
{{ end }}
|
57
view/cluster.templ
Normal file
@ -0,0 +1,57 @@
|
||||
package view
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"git.staur.ca/stobbsm/clustvirt/cluster"
|
||||
"git.staur.ca/stobbsm/clustvirt/view/components"
|
||||
"git.staur.ca/stobbsm/clustvirt/view/layouts"
|
||||
)
|
||||
|
||||
templ ClusterInfo(cs *cluster.ClusterStats, diff cluster.StatDiff, navbar []components.NavItem) {
|
||||
@layouts.Manager("ClustVirt", "Cluster Manager", navbar) {
|
||||
<h3>Cluster Stats</h3>
|
||||
<table class={ "table-auto", "w-full" }>
|
||||
<caption class={ "caption-top" }>
|
||||
CPU stats
|
||||
</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Sockets</th>
|
||||
<th>Cores</th>
|
||||
<th>Threads</th>
|
||||
<th>Allocated</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
{ fmt.Sprint(cs.CPU.Sockets) }
|
||||
</td>
|
||||
<td>
|
||||
{ fmt.Sprint(cs.CPU.Cores) }
|
||||
</td>
|
||||
<td>
|
||||
{ fmt.Sprint(cs.CPU.Threads) }
|
||||
</td>
|
||||
<td>
|
||||
{ fmt.Sprint(cs.CPU.Allocated) }
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
{ fmt.Sprint(diff.CPU.Sockets) }
|
||||
</td>
|
||||
<td>
|
||||
{ fmt.Sprint(diff.CPU.Cores) }
|
||||
</td>
|
||||
<td>
|
||||
{ fmt.Sprint(diff.CPU.Threads) }
|
||||
</td>
|
||||
<td>
|
||||
{ fmt.Sprint(diff.CPU.Allocated) }
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
}
|
195
view/cluster_templ.go
Normal file
@ -0,0 +1,195 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.2.598
|
||||
package view
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
import "github.com/a-h/templ"
|
||||
import "context"
|
||||
import "io"
|
||||
import "bytes"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"git.staur.ca/stobbsm/clustvirt/cluster"
|
||||
"git.staur.ca/stobbsm/clustvirt/view/components"
|
||||
"git.staur.ca/stobbsm/clustvirt/view/layouts"
|
||||
)
|
||||
|
||||
func ClusterInfo(cs *cluster.ClusterStats, diff cluster.StatDiff, navbar []components.NavItem) templ.Component {
|
||||
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
templ_7745c5c3_Buffer = templ.GetBuffer()
|
||||
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
}
|
||||
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_Var2 := templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
templ_7745c5c3_Buffer = templ.GetBuffer()
|
||||
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<h3>Cluster Stats</h3>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var3 = []any{"table-auto", "w-full"}
|
||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var3...)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<table class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ.CSSClasses(templ_7745c5c3_Var3).String()))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var4 = []any{"caption-top"}
|
||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var4...)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<caption class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ.CSSClasses(templ_7745c5c3_Var4).String()))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">CPU stats</caption> <thead><tr><th>Sockets</th><th>Cores</th><th>Threads</th><th>Allocated</th></tr></thead> <tbody><tr><td>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var5 string
|
||||
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprint(cs.CPU.Sockets))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/cluster.templ`, Line: 27, Col: 35}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</td><td>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var6 string
|
||||
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprint(cs.CPU.Cores))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/cluster.templ`, Line: 30, Col: 33}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</td><td>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var7 string
|
||||
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprint(cs.CPU.Threads))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/cluster.templ`, Line: 33, Col: 35}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</td><td>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var8 string
|
||||
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprint(cs.CPU.Allocated))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/cluster.templ`, Line: 36, Col: 37}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</td></tr><tr><td>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var9 string
|
||||
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprint(diff.CPU.Sockets))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/cluster.templ`, Line: 41, Col: 42}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</td><td>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var10 string
|
||||
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprint(diff.CPU.Cores))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/cluster.templ`, Line: 44, Col: 40}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</td><td>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var11 string
|
||||
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprint(diff.CPU.Threads))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/cluster.templ`, Line: 47, Col: 42}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</td><td>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var12 string
|
||||
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprint(diff.CPU.Allocated))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/cluster.templ`, Line: 50, Col: 44}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</td></tr></tbody></table>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
_, templ_7745c5c3_Err = io.Copy(templ_7745c5c3_W, templ_7745c5c3_Buffer)
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
})
|
||||
templ_7745c5c3_Err = layouts.Manager("ClustVirt", "Cluster Manager", navbar).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
})
|
||||
}
|
13
view/components/anchor.templ
Normal file
@ -0,0 +1,13 @@
|
||||
package components
|
||||
|
||||
templ A(href string, text string) {
|
||||
<a href={ templ.URL(href) }>
|
||||
{ text }
|
||||
</a>
|
||||
}
|
||||
|
||||
templ ANewTab(href string, text string) {
|
||||
<a href={ templ.URL(href) } target="_blank">
|
||||
{ text }
|
||||
</a>
|
||||
}
|
103
view/components/anchor_templ.go
Normal file
@ -0,0 +1,103 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.2.598
|
||||
package components
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
import "github.com/a-h/templ"
|
||||
import "context"
|
||||
import "io"
|
||||
import "bytes"
|
||||
|
||||
func A(href string, text string) templ.Component {
|
||||
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
templ_7745c5c3_Buffer = templ.GetBuffer()
|
||||
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
}
|
||||
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_7745c5c3_Buffer.WriteString("<a href=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var2 templ.SafeURL = templ.URL(href)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var2)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var3 string
|
||||
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(text)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/components/anchor.templ`, Line: 4, Col: 10}
|
||||
}
|
||||
_, 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_7745c5c3_Buffer.WriteString("</a>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
})
|
||||
}
|
||||
|
||||
func ANewTab(href string, text string) templ.Component {
|
||||
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
templ_7745c5c3_Buffer = templ.GetBuffer()
|
||||
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var4 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var4 == nil {
|
||||
templ_7745c5c3_Var4 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<a href=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var5 templ.SafeURL = templ.URL(href)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var5)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" target=\"_blank\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var6 string
|
||||
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(text)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/components/anchor.templ`, Line: 10, Col: 10}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</a>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
})
|
||||
}
|
15
view/components/contentlist.templ
Normal file
@ -0,0 +1,15 @@
|
||||
package components
|
||||
|
||||
import "fmt"
|
||||
|
||||
templ List(listImage string) {
|
||||
<ul class={
|
||||
"list-inside",
|
||||
fmt.Sprintf("list-image-%s", listImage) }>
|
||||
{ children... }
|
||||
</ul>
|
||||
}
|
||||
|
||||
templ ListItem() {
|
||||
<li>{ children... }</li>
|
||||
}
|
92
view/components/contentlist_templ.go
Normal file
@ -0,0 +1,92 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.2.598
|
||||
package components
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
import "github.com/a-h/templ"
|
||||
import "context"
|
||||
import "io"
|
||||
import "bytes"
|
||||
|
||||
import "fmt"
|
||||
|
||||
func List(listImage string) templ.Component {
|
||||
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
templ_7745c5c3_Buffer = templ.GetBuffer()
|
||||
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var1 == nil {
|
||||
templ_7745c5c3_Var1 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
var templ_7745c5c3_Var2 = []any{
|
||||
"list-inside",
|
||||
fmt.Sprintf("list-image-%s", listImage)}
|
||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var2...)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<ul class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ.CSSClasses(templ_7745c5c3_Var2).String()))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
|
||||
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_7745c5c3_Buffer.WriteString("</ul>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
})
|
||||
}
|
||||
|
||||
func ListItem() templ.Component {
|
||||
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
templ_7745c5c3_Buffer = templ.GetBuffer()
|
||||
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var3 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var3 == nil {
|
||||
templ_7745c5c3_Var3 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<li>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ_7745c5c3_Var3.Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</li>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
})
|
||||
}
|
5
view/components/contentp.templ
Normal file
@ -0,0 +1,5 @@
|
||||
package components
|
||||
|
||||
templ ContentP() {
|
||||
<p>{ children... }</p>
|
||||
}
|
43
view/components/contentp_templ.go
Normal file
@ -0,0 +1,43 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.2.598
|
||||
package components
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
import "github.com/a-h/templ"
|
||||
import "context"
|
||||
import "io"
|
||||
import "bytes"
|
||||
|
||||
func ContentP() templ.Component {
|
||||
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
templ_7745c5c3_Buffer = templ.GetBuffer()
|
||||
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
}
|
||||
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_7745c5c3_Buffer.WriteString("<p>")
|
||||
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_7745c5c3_Buffer.WriteString("</p>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
})
|
||||
}
|
12
view/components/flexrow.templ
Normal file
@ -0,0 +1,12 @@
|
||||
package components
|
||||
|
||||
templ FlexRow(wrap bool) {
|
||||
<div class={
|
||||
"flex",
|
||||
templ.KV("flex-nowrap", !wrap),
|
||||
templ.KV("flex-wrap", wrap),
|
||||
templ.KV("md:flex-nowrap", wrap)
|
||||
}>
|
||||
{ children... }
|
||||
</div>
|
||||
}
|
60
view/components/flexrow_templ.go
Normal file
@ -0,0 +1,60 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.2.598
|
||||
package components
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
import "github.com/a-h/templ"
|
||||
import "context"
|
||||
import "io"
|
||||
import "bytes"
|
||||
|
||||
func FlexRow(wrap bool) templ.Component {
|
||||
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
templ_7745c5c3_Buffer = templ.GetBuffer()
|
||||
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var1 == nil {
|
||||
templ_7745c5c3_Var1 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
var templ_7745c5c3_Var2 = []any{
|
||||
"flex",
|
||||
templ.KV("flex-nowrap", !wrap),
|
||||
templ.KV("flex-wrap", wrap),
|
||||
templ.KV("md:flex-nowrap", wrap)}
|
||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var2...)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ.CSSClasses(templ_7745c5c3_Var2).String()))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
|
||||
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_7745c5c3_Buffer.WriteString("</div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
})
|
||||
}
|
5
view/components/hero.templ
Normal file
@ -0,0 +1,5 @@
|
||||
package components
|
||||
|
||||
templ Hero() {
|
||||
{ children... }
|
||||
}
|
35
view/components/hero_templ.go
Normal file
@ -0,0 +1,35 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.2.598
|
||||
package components
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
import "github.com/a-h/templ"
|
||||
import "context"
|
||||
import "io"
|
||||
import "bytes"
|
||||
|
||||
func Hero() templ.Component {
|
||||
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
templ_7745c5c3_Buffer = templ.GetBuffer()
|
||||
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
}
|
||||
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_7745c5c3_Var1.Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
})
|
||||
}
|
24
view/components/infobox.templ
Normal file
@ -0,0 +1,24 @@
|
||||
package components
|
||||
|
||||
import "fmt"
|
||||
|
||||
type InfoBoxConfig struct {
|
||||
ColourClass string
|
||||
InFlex bool
|
||||
FlexBasis string
|
||||
}
|
||||
|
||||
templ InfoBox(title string, colour string) {
|
||||
<div class={
|
||||
"infobox",
|
||||
"h-full",
|
||||
fmt.Sprintf("bg-%s-900", colour),
|
||||
fmt.Sprintf("text-%s-100", colour),
|
||||
fmt.Sprintf("shadow-%s-900", colour),
|
||||
fmt.Sprintf("divide-%s-800", colour),
|
||||
fmt.Sprintf("border-%s-800", colour),
|
||||
}>
|
||||
<h3 class="text-xl font-semibold">{ title }</h3>
|
||||
{ children... }
|
||||
</div>
|
||||
}
|
85
view/components/infobox_templ.go
Normal file
@ -0,0 +1,85 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.2.598
|
||||
package components
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
import "github.com/a-h/templ"
|
||||
import "context"
|
||||
import "io"
|
||||
import "bytes"
|
||||
|
||||
import "fmt"
|
||||
|
||||
type InfoBoxConfig struct {
|
||||
ColourClass string
|
||||
InFlex bool
|
||||
FlexBasis string
|
||||
}
|
||||
|
||||
func InfoBox(title string, colour string) templ.Component {
|
||||
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
templ_7745c5c3_Buffer = templ.GetBuffer()
|
||||
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var1 == nil {
|
||||
templ_7745c5c3_Var1 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
var templ_7745c5c3_Var2 = []any{
|
||||
"infobox",
|
||||
"h-full",
|
||||
fmt.Sprintf("bg-%s-900", colour),
|
||||
fmt.Sprintf("text-%s-100", colour),
|
||||
fmt.Sprintf("shadow-%s-900", colour),
|
||||
fmt.Sprintf("divide-%s-800", colour),
|
||||
fmt.Sprintf("border-%s-800", colour),
|
||||
}
|
||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var2...)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ.CSSClasses(templ_7745c5c3_Var2).String()))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><h3 class=\"text-xl font-semibold\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var3 string
|
||||
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(title)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/components/infobox.templ`, Line: 20, Col: 43}
|
||||
}
|
||||
_, 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_7745c5c3_Buffer.WriteString("</h3>")
|
||||
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_7745c5c3_Buffer.WriteString("</div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
})
|
||||
}
|
40
view/components/navbar.templ
Normal file
@ -0,0 +1,40 @@
|
||||
package components
|
||||
|
||||
templ NavBar(hero templ.Component, navItems []NavItem) {
|
||||
<nav
|
||||
class={ "w-100",
|
||||
"bg-uigrey-600",
|
||||
"shadow",
|
||||
"shadow-uigrey-700",
|
||||
"rounded",
|
||||
"rounded-full",
|
||||
"md:px-auto" }
|
||||
>
|
||||
<div
|
||||
class={ "md:h-16",
|
||||
"h-28",
|
||||
"mx-auto",
|
||||
"md:px-4",
|
||||
"container",
|
||||
"flex",
|
||||
"items-center",
|
||||
"justify-between",
|
||||
"flex-wrap",
|
||||
"md:flex-nowrap" }
|
||||
>
|
||||
<a href="/">
|
||||
@hero
|
||||
</a>
|
||||
<div class={ "text-uigrey-200", "font-semibold", "md:w-auto", "md:order-2", "order-3" }>
|
||||
<ul class={ "flex", "justify-between", "sm:w-full", "md:w-auto" }>
|
||||
for _, ni := range navItems {
|
||||
<li class={ "hover:text-uiblue-200", "md:px-4", "md:py-2" }>
|
||||
<a href={ templ.URL(ni.Href) }>{ ni.Name }</a>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
<div class={ "order-2", "md:order-3" }>Login</div>
|
||||
</div>
|
||||
</nav>
|
||||
}
|
184
view/components/navbar_templ.go
Normal file
@ -0,0 +1,184 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.2.598
|
||||
package components
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
import "github.com/a-h/templ"
|
||||
import "context"
|
||||
import "io"
|
||||
import "bytes"
|
||||
|
||||
func NavBar(hero templ.Component, navItems []NavItem) templ.Component {
|
||||
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
templ_7745c5c3_Buffer = templ.GetBuffer()
|
||||
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var1 == nil {
|
||||
templ_7745c5c3_Var1 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
var templ_7745c5c3_Var2 = []any{"w-100",
|
||||
"bg-uigrey-600",
|
||||
"shadow",
|
||||
"shadow-uigrey-700",
|
||||
"rounded",
|
||||
"rounded-full",
|
||||
"md:px-auto"}
|
||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var2...)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<nav class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ.CSSClasses(templ_7745c5c3_Var2).String()))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var3 = []any{"md:h-16",
|
||||
"h-28",
|
||||
"mx-auto",
|
||||
"md:px-4",
|
||||
"container",
|
||||
"flex",
|
||||
"items-center",
|
||||
"justify-between",
|
||||
"flex-wrap",
|
||||
"md:flex-nowrap"}
|
||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var3...)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ.CSSClasses(templ_7745c5c3_Var3).String()))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><a href=\"/\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = hero.Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</a>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var4 = []any{"text-uigrey-200", "font-semibold", "md:w-auto", "md:order-2", "order-3"}
|
||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var4...)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ.CSSClasses(templ_7745c5c3_Var4).String()))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var5 = []any{"flex", "justify-between", "sm:w-full", "md:w-auto"}
|
||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var5...)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<ul class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ.CSSClasses(templ_7745c5c3_Var5).String()))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
for _, ni := range navItems {
|
||||
var templ_7745c5c3_Var6 = []any{"hover:text-uiblue-200", "md:px-4", "md:py-2"}
|
||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var6...)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<li class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ.CSSClasses(templ_7745c5c3_Var6).String()))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><a href=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var7 templ.SafeURL = templ.URL(ni.Href)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var7)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var8 string
|
||||
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(ni.Name)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/components/navbar.templ`, Line: 31, Col: 47}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</a></li>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</ul></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var9 = []any{"order-2", "md:order-3"}
|
||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var9...)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ.CSSClasses(templ_7745c5c3_Var9).String()))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">Login</div></div></nav>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
})
|
||||
}
|
14
view/components/util.go
Normal file
@ -0,0 +1,14 @@
|
||||
package components
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type NavItem struct {
|
||||
Name string
|
||||
Href string
|
||||
}
|
||||
|
||||
func NoWrapSize(size string) string {
|
||||
return fmt.Sprintf("%s:flex-nowrap", size)
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
{{ define "content" }}
|
||||
<h1>{{.HostName}}</h1>
|
||||
<p>This will contain all the graphs, storage pool lists, vm lists, etc for the host selected.</p>
|
||||
{{ end }}
|
133
view/hostinfo.templ
Normal file
@ -0,0 +1,133 @@
|
||||
package view
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"git.staur.ca/stobbsm/clustvirt/lib/host"
|
||||
"git.staur.ca/stobbsm/clustvirt/view/components"
|
||||
"git.staur.ca/stobbsm/clustvirt/view/layouts"
|
||||
)
|
||||
|
||||
script memchart(used uint64, free uint64, buf uint64, cache uint64) {
|
||||
ctx = document.getElementById('memchart');
|
||||
|
||||
new Chart(ctx, {
|
||||
type: 'pie',
|
||||
data: {
|
||||
labels: [
|
||||
'Used',
|
||||
'Free',
|
||||
'Cached',
|
||||
'Buffered'
|
||||
],
|
||||
datasets: [{
|
||||
label: 'Memory Usage',
|
||||
data: [
|
||||
used,
|
||||
free,
|
||||
cache,
|
||||
buf,
|
||||
],
|
||||
backgroundColor: [
|
||||
'rgb(255,0,0)',
|
||||
'rgb(0,255,0)',
|
||||
'rgb(128,128,0)',
|
||||
'rgb(0,0,255)'
|
||||
],
|
||||
hoverOffset: 4
|
||||
}],
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// MemChart get's the memory pi chart from the host
|
||||
templ MemChart(h *host.Host) {
|
||||
<div class={ "flex" , "flex-row" }>
|
||||
<div class={ "size-40" , "md:size-80" }>
|
||||
<canvas id="memchart" class={ "size-40" , "md:size-80" }></canvas>
|
||||
</div>
|
||||
<ul>
|
||||
<li>Free: <span id="memfree">{ fmt.Sprintf("%d Gi", h.NodeMemory.Free/1024/1024) }</span></li>
|
||||
<li>Cached: <span id="memcach">{ fmt.Sprintf("%d Gi", h.NodeMemory.Cached/1024/1024) }</span></li>
|
||||
<li>Buffers: <span id="membuf">{ fmt.Sprintf("%d Gi", h.NodeMemory.Buffers/1024/1024) }</span></li>
|
||||
<li>Total: <span id="memtot">{ fmt.Sprintf("%d Gi", h.NodeMemory.Total/1024/1024) }</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
@memchart(h.NodeMemory.Total-h.NodeMemory.Free-h.NodeMemory.Buffers-h.NodeMemory.Cached, h.NodeMemory.Free,
|
||||
h.NodeMemory.Buffers, h.NodeMemory.Cached)
|
||||
}
|
||||
|
||||
templ hostButton(hostname string) {
|
||||
<button
|
||||
hx-target="#sysContent"
|
||||
hx-get={ fmt.Sprintf("/htmx/host/%s", hostname) }
|
||||
class={ "rounded",
|
||||
"border",
|
||||
"border-solid",
|
||||
"border-uiblue-700",
|
||||
"text-uigrey-200",
|
||||
"bg-uiblue-800",
|
||||
"hover:border-uipurple-400",
|
||||
"hover:text-ui-grey-800",
|
||||
"hover:bg-uipurple-600",
|
||||
"gap-2",
|
||||
"p-1",
|
||||
"my-1",
|
||||
"w-full",
|
||||
"flex",
|
||||
"flex-row",
|
||||
"justify-between" }
|
||||
>
|
||||
<span class={ "px-2" }>{ hostname }</span>
|
||||
<img class={ "htmx-indicator" , "inline-block" , "h-6" , "px-2" } src="/static/images/bars.svg"/>
|
||||
</button>
|
||||
}
|
||||
|
||||
// HostConnect is the page that allows us to select a host to get information from
|
||||
templ HostMain(navBarItems []components.NavItem) {
|
||||
@layouts.Manager("ClustVirt", "Cluster Manager", navBarItems) {
|
||||
<div class={ "flex" , "flex-row" , "h-full" , "mt-2" }>
|
||||
<div id="sysNavBar" class={ "w-1/6" , "border-uigrey-600" , "border" , "rounded" , "border-dotted" , "p-2" }>
|
||||
<ul>
|
||||
<li>
|
||||
@hostButton("venus.staur.ca")
|
||||
</li>
|
||||
<li>
|
||||
@hostButton("earth.staur.ca")
|
||||
</li>
|
||||
<li>
|
||||
@hostButton("mars.staur.ca")
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div id="sysContent" class={ "w-3/4" , "px-2" }>
|
||||
<p>This is where you can see a system overview of all available hosts</p>
|
||||
<p>
|
||||
For now, there is just this simple box to choose a host to connect to
|
||||
and push the button to load the system information via HTMX
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
// HostInfo is meant to be an HTMX response
|
||||
templ HostInfo(h *host.Host) {
|
||||
<div class={ "flex" , "flex-col" }>
|
||||
<div
|
||||
class={ "flex" , "flex-row" , "justify-start" , "px-2" }
|
||||
hx-trigger="every 30s"
|
||||
hx-get={ fmt.Sprintf("/htmx/host/%s/stats", h.HostName) }
|
||||
hx-target="#sysInfo"
|
||||
>
|
||||
<h3>{ h.HostName }</h3>
|
||||
<img class={ "h-6" , "px-2" , "htmx-indicator" , "inline-block" } src="/static/images/grid.svg"/>
|
||||
</div>
|
||||
<div class={ "flex" , "flex-row" , "flex-wrap" } id="sysInfo">
|
||||
@SysInfo(h)
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
templ SysInfo(h *host.Host) {
|
||||
@MemChart(h)
|
||||
}
|
545
view/hostinfo_templ.go
Normal file
@ -0,0 +1,545 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.2.598
|
||||
package view
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
import "github.com/a-h/templ"
|
||||
import "context"
|
||||
import "io"
|
||||
import "bytes"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"git.staur.ca/stobbsm/clustvirt/lib/host"
|
||||
"git.staur.ca/stobbsm/clustvirt/view/components"
|
||||
"git.staur.ca/stobbsm/clustvirt/view/layouts"
|
||||
)
|
||||
|
||||
func memchart(used uint64, free uint64, buf uint64, cache uint64) templ.ComponentScript {
|
||||
return templ.ComponentScript{
|
||||
Name: `__templ_memchart_9d4c`,
|
||||
Function: `function __templ_memchart_9d4c(used, free, buf, cache){ctx = document.getElementById('memchart');
|
||||
|
||||
new Chart(ctx, {
|
||||
type: 'pie',
|
||||
data: {
|
||||
labels: [
|
||||
'Used',
|
||||
'Free',
|
||||
'Cached',
|
||||
'Buffered'
|
||||
],
|
||||
datasets: [{
|
||||
label: 'Memory Usage',
|
||||
data: [
|
||||
used,
|
||||
free,
|
||||
cache,
|
||||
buf,
|
||||
],
|
||||
backgroundColor: [
|
||||
'rgb(255,0,0)',
|
||||
'rgb(0,255,0)',
|
||||
'rgb(128,128,0)',
|
||||
'rgb(0,0,255)'
|
||||
],
|
||||
hoverOffset: 4
|
||||
}],
|
||||
}
|
||||
});
|
||||
}`,
|
||||
Call: templ.SafeScript(`__templ_memchart_9d4c`, used, free, buf, cache),
|
||||
CallInline: templ.SafeScriptInline(`__templ_memchart_9d4c`, used, free, buf, cache),
|
||||
}
|
||||
}
|
||||
|
||||
// MemChart get's the memory pi chart from the host
|
||||
func MemChart(h *host.Host) templ.Component {
|
||||
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
templ_7745c5c3_Buffer = templ.GetBuffer()
|
||||
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var1 == nil {
|
||||
templ_7745c5c3_Var1 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
var templ_7745c5c3_Var2 = []any{"flex", "flex-row"}
|
||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var2...)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ.CSSClasses(templ_7745c5c3_Var2).String()))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var3 = []any{"size-40", "md:size-80"}
|
||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var3...)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ.CSSClasses(templ_7745c5c3_Var3).String()))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var4 = []any{"size-40", "md:size-80"}
|
||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var4...)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<canvas id=\"memchart\" class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ.CSSClasses(templ_7745c5c3_Var4).String()))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"></canvas></div><ul><li>Free: <span id=\"memfree\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var5 string
|
||||
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d Gi", h.NodeMemory.Free/1024/1024))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/hostinfo.templ`, Line: 48, Col: 83}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</span></li><li>Cached: <span id=\"memcach\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var6 string
|
||||
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d Gi", h.NodeMemory.Cached/1024/1024))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/hostinfo.templ`, Line: 49, Col: 87}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</span></li><li>Buffers: <span id=\"membuf\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var7 string
|
||||
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d Gi", h.NodeMemory.Buffers/1024/1024))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/hostinfo.templ`, Line: 50, Col: 88}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</span></li><li>Total: <span id=\"memtot\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var8 string
|
||||
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d Gi", h.NodeMemory.Total/1024/1024))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/hostinfo.templ`, Line: 51, Col: 84}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</span></li></ul></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = memchart(h.NodeMemory.Total-h.NodeMemory.Free-h.NodeMemory.Buffers-h.NodeMemory.Cached, h.NodeMemory.Free,
|
||||
h.NodeMemory.Buffers, h.NodeMemory.Cached).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
})
|
||||
}
|
||||
|
||||
func hostButton(hostname string) templ.Component {
|
||||
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
templ_7745c5c3_Buffer = templ.GetBuffer()
|
||||
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var9 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var9 == nil {
|
||||
templ_7745c5c3_Var9 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
var templ_7745c5c3_Var10 = []any{"rounded",
|
||||
"border",
|
||||
"border-solid",
|
||||
"border-uiblue-700",
|
||||
"text-uigrey-200",
|
||||
"bg-uiblue-800",
|
||||
"hover:border-uipurple-400",
|
||||
"hover:text-ui-grey-800",
|
||||
"hover:bg-uipurple-600",
|
||||
"gap-2",
|
||||
"p-1",
|
||||
"my-1",
|
||||
"w-full",
|
||||
"flex",
|
||||
"flex-row",
|
||||
"justify-between"}
|
||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var10...)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<button hx-target=\"#sysContent\" hx-get=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(fmt.Sprintf("/htmx/host/%s", hostname)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ.CSSClasses(templ_7745c5c3_Var10).String()))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var11 = []any{"px-2"}
|
||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var11...)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<span class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ.CSSClasses(templ_7745c5c3_Var11).String()))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var12 string
|
||||
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(hostname)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/hostinfo.templ`, Line: 79, Col: 35}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</span> ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var13 = []any{"htmx-indicator", "inline-block", "h-6", "px-2"}
|
||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var13...)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<img class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ.CSSClasses(templ_7745c5c3_Var13).String()))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" src=\"/static/images/bars.svg\"></button>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
})
|
||||
}
|
||||
|
||||
// HostConnect is the page that allows us to select a host to get information from
|
||||
func HostMain(navBarItems []components.NavItem) templ.Component {
|
||||
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
templ_7745c5c3_Buffer = templ.GetBuffer()
|
||||
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var14 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var14 == nil {
|
||||
templ_7745c5c3_Var14 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
templ_7745c5c3_Var15 := templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
templ_7745c5c3_Buffer = templ.GetBuffer()
|
||||
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
}
|
||||
var templ_7745c5c3_Var16 = []any{"flex", "flex-row", "h-full", "mt-2"}
|
||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var16...)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ.CSSClasses(templ_7745c5c3_Var16).String()))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var17 = []any{"w-1/6", "border-uigrey-600", "border", "rounded", "border-dotted", "p-2"}
|
||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var17...)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div id=\"sysNavBar\" class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ.CSSClasses(templ_7745c5c3_Var17).String()))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><ul><li>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = hostButton("venus.staur.ca").Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</li><li>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = hostButton("earth.staur.ca").Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</li><li>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = hostButton("mars.staur.ca").Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</li></ul></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var18 = []any{"w-3/4", "px-2"}
|
||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var18...)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div id=\"sysContent\" class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ.CSSClasses(templ_7745c5c3_Var18).String()))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><p>This is where you can see a system overview of all available hosts</p><p>For now, there is just this simple box to choose a host to connect to and push the button to load the system information via HTMX</p></div></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
_, templ_7745c5c3_Err = io.Copy(templ_7745c5c3_W, templ_7745c5c3_Buffer)
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
})
|
||||
templ_7745c5c3_Err = layouts.Manager("ClustVirt", "Cluster Manager", navBarItems).Render(templ.WithChildren(ctx, templ_7745c5c3_Var15), templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
})
|
||||
}
|
||||
|
||||
// HostInfo is meant to be an HTMX response
|
||||
func HostInfo(h *host.Host) templ.Component {
|
||||
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
templ_7745c5c3_Buffer = templ.GetBuffer()
|
||||
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var19 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var19 == nil {
|
||||
templ_7745c5c3_Var19 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
var templ_7745c5c3_Var20 = []any{"flex", "flex-col"}
|
||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var20...)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ.CSSClasses(templ_7745c5c3_Var20).String()))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var21 = []any{"flex", "flex-row", "justify-start", "px-2"}
|
||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var21...)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ.CSSClasses(templ_7745c5c3_Var21).String()))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" hx-trigger=\"every 30s\" hx-get=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(fmt.Sprintf("/htmx/host/%s/stats", h.HostName)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" hx-target=\"#sysInfo\"><h3>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var22 string
|
||||
templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(h.HostName)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/hostinfo.templ`, Line: 121, Col: 19}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</h3>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var23 = []any{"h-6", "px-2", "htmx-indicator", "inline-block"}
|
||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var23...)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<img class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ.CSSClasses(templ_7745c5c3_Var23).String()))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" src=\"/static/images/grid.svg\"></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var24 = []any{"flex", "flex-row", "flex-wrap"}
|
||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var24...)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ.CSSClasses(templ_7745c5c3_Var24).String()))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" id=\"sysInfo\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = SysInfo(h).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
})
|
||||
}
|
||||
|
||||
func SysInfo(h *host.Host) templ.Component {
|
||||
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
templ_7745c5c3_Buffer = templ.GetBuffer()
|
||||
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var25 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var25 == nil {
|
||||
templ_7745c5c3_Var25 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
templ_7745c5c3_Err = MemChart(h).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
})
|
||||
}
|
7
view/layouts/common.go
Normal file
@ -0,0 +1,7 @@
|
||||
package layouts
|
||||
|
||||
// LayoutConfig specifics common elements of layouts
|
||||
type LayoutConfig struct {
|
||||
MainTitle string
|
||||
SubTitle string
|
||||
}
|
30
view/layouts/manager.templ
Normal file
@ -0,0 +1,30 @@
|
||||
package layouts
|
||||
|
||||
import "git.staur.ca/stobbsm/clustvirt/view/components"
|
||||
|
||||
templ Manager(title string, subtitle string, navBarItem []components.NavItem) {
|
||||
<!DOCTYPE html>
|
||||
<html class={ "text-slate-50" , "bg-slate-900" }>
|
||||
<head>
|
||||
<title>ClustVirt</title>
|
||||
<link href="/static/css/style.css" type="text/css" rel="stylesheet"/>
|
||||
</head>
|
||||
<body>
|
||||
<div class={ "flex" , "flex-col" , "h-screen" }>
|
||||
<header class={ "px-4" }>
|
||||
@header(title, subtitle, navBarItem)
|
||||
</header>
|
||||
<main class={ "px-4" , "h-full" }>
|
||||
{ children... }
|
||||
</main>
|
||||
<footer class={ "px-4" , "bottom-0", "w-full" , "justify-self-end" }>
|
||||
@footer()
|
||||
</footer>
|
||||
</div>
|
||||
<!-- Load HTMX -->
|
||||
<script src=" https://unpkg.com/htmx.org@1.9.11" integrity="sha384-0gxUXCCR8yv9FM2b+U3FDbsKthCI66oH5IA9fHppQq9DDMHuMauqq1ZHBpJxQ0J0" crossorigin="anonymous">
|
||||
</script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.2/dist/chart.umd.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
}
|
146
view/layouts/manager_templ.go
Normal file
@ -0,0 +1,146 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.2.598
|
||||
package layouts
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
import "github.com/a-h/templ"
|
||||
import "context"
|
||||
import "io"
|
||||
import "bytes"
|
||||
|
||||
import "git.staur.ca/stobbsm/clustvirt/view/components"
|
||||
|
||||
func Manager(title string, subtitle string, navBarItem []components.NavItem) templ.Component {
|
||||
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
templ_7745c5c3_Buffer = templ.GetBuffer()
|
||||
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
}
|
||||
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_7745c5c3_Buffer.WriteString("<!doctype html>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var2 = []any{"text-slate-50", "bg-slate-900"}
|
||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var2...)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<html class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ.CSSClasses(templ_7745c5c3_Var2).String()))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><head><title>ClustVirt</title><link href=\"/static/css/style.css\" type=\"text/css\" rel=\"stylesheet\"></head><body>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var3 = []any{"flex", "flex-col", "h-screen"}
|
||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var3...)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ.CSSClasses(templ_7745c5c3_Var3).String()))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var4 = []any{"px-4"}
|
||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var4...)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<header class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ.CSSClasses(templ_7745c5c3_Var4).String()))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = header(title, subtitle, navBarItem).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</header>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var5 = []any{"px-4", "h-full"}
|
||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var5...)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<main class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ.CSSClasses(templ_7745c5c3_Var5).String()))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
|
||||
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_7745c5c3_Buffer.WriteString("</main>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var6 = []any{"px-4", "bottom-0", "w-full", "justify-self-end"}
|
||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var6...)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<footer class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ.CSSClasses(templ_7745c5c3_Var6).String()))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = footer().Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</footer></div><!-- Load HTMX --><script src=\" https://unpkg.com/htmx.org@1.9.11\" integrity=\"sha384-0gxUXCCR8yv9FM2b+U3FDbsKthCI66oH5IA9fHppQq9DDMHuMauqq1ZHBpJxQ0J0\" crossorigin=\"anonymous\">\n </script><script src=\"https://cdn.jsdelivr.net/npm/chart.js@4.4.2/dist/chart.umd.min.js\"></script></body></html>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
})
|
||||
}
|
41
view/layouts/staticpage.templ
Normal file
@ -0,0 +1,41 @@
|
||||
package layouts
|
||||
|
||||
import "git.staur.ca/stobbsm/clustvirt/view/components"
|
||||
|
||||
templ StaticPage(title string, subtitle string, navBarItems []components.NavItem) {
|
||||
<!DOCTYPE html>
|
||||
<html class={ "text-uigrey-100" , "bg-uigrey-900" }>
|
||||
<head>
|
||||
<title>Clustvirt</title>
|
||||
<link href="/static/css/style.css" type="text/css" rel="stylesheet"/>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
@header(title, subtitle, navBarItems)
|
||||
</header>
|
||||
<div id="content" name="content" class={ "flex" , "flex-col" , "gap-2" }>
|
||||
{ children... }
|
||||
</div>
|
||||
<footer>
|
||||
@footer()
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
}
|
||||
|
||||
templ hero(title string) {
|
||||
<h1 class={ "text-2xl", "font-bold", "text-uiblue-200", "md:order-1" }>{ title }</h1>
|
||||
}
|
||||
|
||||
templ header(title string, subtitle string, navBarItems []components.NavItem) {
|
||||
<h2 class={ "text-lg" , "font-semibold" , "italic" , "h-6" }>{ subtitle }</h2>
|
||||
@components.NavBar(hero(title), navBarItems)
|
||||
}
|
||||
|
||||
templ footer() {
|
||||
<div class="flex gap-4 md:gap-6 sm:gap-8 divide divide-solid">
|
||||
<div id="footer_left" class="flex-auto basis-1/4 md:basis-1/3 p-2">Left</div>
|
||||
<div id="footer_middle" class="flex-auto basis-1/2 md:basis-1/3 p-2">Middle</div>
|
||||
<div id="footer_right" class="flex-auto basis-1/4 md:basis-1/3 p-2">Right</div>
|
||||
</div>
|
||||
}
|
223
view/layouts/staticpage_templ.go
Normal file
@ -0,0 +1,223 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.2.598
|
||||
package layouts
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
import "github.com/a-h/templ"
|
||||
import "context"
|
||||
import "io"
|
||||
import "bytes"
|
||||
|
||||
import "git.staur.ca/stobbsm/clustvirt/view/components"
|
||||
|
||||
func StaticPage(title string, subtitle string, navBarItems []components.NavItem) templ.Component {
|
||||
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
templ_7745c5c3_Buffer = templ.GetBuffer()
|
||||
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
}
|
||||
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_7745c5c3_Buffer.WriteString("<!doctype html>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var2 = []any{"text-uigrey-100", "bg-uigrey-900"}
|
||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var2...)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<html class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ.CSSClasses(templ_7745c5c3_Var2).String()))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><head><title>Clustvirt</title><link href=\"/static/css/style.css\" type=\"text/css\" rel=\"stylesheet\"></head><body><header>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = header(title, subtitle, navBarItems).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</header>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var3 = []any{"flex", "flex-col", "gap-2"}
|
||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var3...)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div id=\"content\" name=\"content\" class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ.CSSClasses(templ_7745c5c3_Var3).String()))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
|
||||
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_7745c5c3_Buffer.WriteString("</div><footer>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = footer().Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</footer></body></html>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
})
|
||||
}
|
||||
|
||||
func hero(title string) templ.Component {
|
||||
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
templ_7745c5c3_Buffer = templ.GetBuffer()
|
||||
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var4 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var4 == nil {
|
||||
templ_7745c5c3_Var4 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
var templ_7745c5c3_Var5 = []any{"text-2xl", "font-bold", "text-uiblue-200", "md:order-1"}
|
||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var5...)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<h1 class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ.CSSClasses(templ_7745c5c3_Var5).String()))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var6 string
|
||||
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(title)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/layouts/staticpage.templ`, Line: 26, Col: 80}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</h1>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
})
|
||||
}
|
||||
|
||||
func header(title string, subtitle string, navBarItems []components.NavItem) templ.Component {
|
||||
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
templ_7745c5c3_Buffer = templ.GetBuffer()
|
||||
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var7 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var7 == nil {
|
||||
templ_7745c5c3_Var7 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
var templ_7745c5c3_Var8 = []any{"text-lg", "font-semibold", "italic", "h-6"}
|
||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var8...)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<h2 class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ.CSSClasses(templ_7745c5c3_Var8).String()))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var9 string
|
||||
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(subtitle)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/layouts/staticpage.templ`, Line: 30, Col: 72}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</h2>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = components.NavBar(hero(title), navBarItems).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
})
|
||||
}
|
||||
|
||||
func footer() templ.Component {
|
||||
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
templ_7745c5c3_Buffer = templ.GetBuffer()
|
||||
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var10 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var10 == nil {
|
||||
templ_7745c5c3_Var10 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"flex gap-4 md:gap-6 sm:gap-8 divide divide-solid\"><div id=\"footer_left\" class=\"flex-auto basis-1/4 md:basis-1/3 p-2\">Left</div><div id=\"footer_middle\" class=\"flex-auto basis-1/2 md:basis-1/3 p-2\">Middle</div><div id=\"footer_right\" class=\"flex-auto basis-1/4 md:basis-1/3 p-2\">Right</div></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
})
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
package view
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Regular pages are defined as views here, like the homepage
|
||||
|
||||
// Major components of each page.
|
||||
const (
|
||||
index = `view/_index.gohtml`
|
||||
header = `view/_header.gohtml`
|
||||
footer = `view/_footer.gohtml`
|
||||
)
|
||||
|
||||
// These constitute the static parts of the site that don't need to change, loaded as a template for rendering
|
||||
const (
|
||||
home = `view/static/home.gohtml`
|
||||
)
|
||||
|
||||
func init() {
|
||||
if fi, err := os.Stat(index); err != nil {
|
||||
log.Fatal(fi.Name(), fi.IsDir(), err)
|
||||
}
|
||||
if fi, err := os.Stat(header); err != nil {
|
||||
log.Fatal(fi.Name(), fi.IsDir(), err)
|
||||
}
|
||||
if fi, err := os.Stat(footer); err != nil {
|
||||
log.Fatal(fi.Name(), fi.IsDir(), err)
|
||||
}
|
||||
if fi, err := os.Stat(home); err != nil {
|
||||
log.Fatal(fi.Name(), fi.IsDir(), err)
|
||||
}}
|
||||
|
||||
var ViewHome *View
|
||||
|
||||
func init() {
|
||||
log.Println("Initializing homepage")
|
||||
var err error
|
||||
ViewHome, err = NewFromFile(home)
|
||||
log.Println(err)
|
||||
}
|
@ -1,166 +0,0 @@
|
||||
{{ define "content" }}
|
||||
<div class="m-2 px-4 pt-2 flex-col shadow-md
|
||||
rounded-2xl border-8 border-double divide-solid divide-y-2
|
||||
bg-dracula-900 text-dracula-100 shadow-dracula-900
|
||||
divide-dracula-800 border-dracula-800">
|
||||
<h3 class="text-xl font-semibold">What is this?</h3>
|
||||
<p>Clustvirt (work in progress name) aims to be the agnostic
|
||||
cluster controller for libvirtd. The server component is
|
||||
used to display both the WebUI and run the REST API used to
|
||||
control one to many libvirtd hosts to manage virual machines,
|
||||
LXC containers (through libvirtd), gather information about
|
||||
each host, and monitor each host.</p>
|
||||
</div>
|
||||
<div class="m-2 px-4 pt-2 flex-col shadow-md
|
||||
rounded-2xl border-8 border-double divide-solid divide-y-2
|
||||
bg-blade-900 text-blade-100 shadow-blade-900
|
||||
divide-blade-800 border-blade-800">
|
||||
<h3 class="text-xl font-semibold">Project Goals</h4>
|
||||
<ul class="leading-relaxed list-image-accepted list-inside">
|
||||
<li class="accepted">Open source, currently on the MIT license</li>
|
||||
<li class="notimpl">Base OS Agnostic. If it can run libvirtd, this should be
|
||||
able to control it on some level</li>
|
||||
<li class="notimpl">Control the Virtual Machine life cycle
|
||||
on one or more libvirtd hosts</li>
|
||||
<li class="notimpl">Add clusting capabilities to libvirtd host,
|
||||
including;</li>
|
||||
<li class="notimpl">Migration of VMs</li>
|
||||
<li class="notimpl">Syncronizing secrets</li>
|
||||
<li class="notimpl">Syncronizing VLANs, bridges, host only
|
||||
networking</li>
|
||||
<li class="notimpl">Sharing HA storage availability</li>
|
||||
<li class="notimpl">Locking shared resources like disks</li>
|
||||
<li class="notimpl">Starting VMs marked for HA on another host
|
||||
when one goes down</li>
|
||||
<li class="notimpl">Manage a library of Cloud-init resources
|
||||
and templates to build new VMs quickly</li>
|
||||
<li class="notimpl">Local Storage management, including local
|
||||
directory, lvm, zfs (if installed)</li>
|
||||
<li class="notimpl">Advanced Storage management, such as Ceph,
|
||||
glusterfs, drbd, iscsi, nfs</li>
|
||||
<li class="notimpl">Storage syncronization of local disks
|
||||
between hosts (zfs snapshots, lvm snapshots, rsync)</li>
|
||||
<li class="notimpl">Backup scheduling, creation, restoration</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="m-2 px-4 pt-2 flex-col shadow-md
|
||||
rounded-2xl border-8 border-double divide-solid divide-y-2
|
||||
bg-cullen-900 text-cullen-100 shadow-cullen-900
|
||||
divide-cullen-800 border-cullen-800">
|
||||
<h3 class="text-xl font-semibold">Stretch Goals</h3>
|
||||
<ul class="leading-relaxed list-image-possible list-inside">
|
||||
<li>Install the OS which libvirtd is running on</li>
|
||||
<li>Install/provision libvirtd on a host that does not have
|
||||
it installed</li>
|
||||
<li>Tools to move from one vendor to clustvirt/libvirtd</li>
|
||||
<li>VM templates for common aspects of VM creation and management,
|
||||
like appliances</li>
|
||||
<li>External tool access that can be used to manage things that
|
||||
are not managed here (cephadm dashboard, for instance)</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="m-2 px-4 pt-2 flex-col shadow-md
|
||||
rounded-2xl border-8 border-double divide-solid divide-y-2
|
||||
bg-morbius-900 text-morbius-100 shadow-morbius-900
|
||||
divide-morbius-800 border-morbius-800">
|
||||
<h3 class="text-xl font-semibold">Reddit Requested Features</h3>
|
||||
<ul class="list-image-possible list-inside leading-relaxed">
|
||||
<li>Search/Filter on hosts/vms - @Lopsided_Speaker_553</li>
|
||||
<li>Balance on resource usage per host/Automattically migrate to
|
||||
least used host - @Lopsided_Speaker_553</li>
|
||||
<li>Support inter-vm only commmunication (VxLAN style)
|
||||
- @Lopsided_Speaker_553</li>
|
||||
<li>Deploy VMs using only API - @Lopsided_Speaker_553</li>
|
||||
<li>Well documented, first class API - @kasperlitheater</li>
|
||||
<li>Bootstrap service to configure a new server - @phatpappa_</li>
|
||||
<li>For the love of kitten, don't use XML as configuration files
|
||||
- @pascalbrax</li>
|
||||
<li>Expose the Cluster Manager functionalities as API - @raven2611</li>
|
||||
<li>CPU architecture awareness for migrations - @raven2611</li>
|
||||
<li>Inter VM Communications via VXLAN/EVPN - @raven2611</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="m-2 px-4 pt-2 flex-col shadow-md
|
||||
rounded-2xl border-8 border-double divide-solid divide-y-2
|
||||
bg-marcelin-900 text-marcelin-100 shadow-marcelin-900
|
||||
divide-marcelin-800 border-marcelin-800">
|
||||
<h3 class="text-xl font-semibold">Never Going to Happen</h3>
|
||||
<ul class="list-image-never list-inside leading-relaxed">
|
||||
<li>Kubernetes</li>
|
||||
<li>Application container management (docker, podman, etc)</li>
|
||||
<li>Become an OS</li>
|
||||
<li>Have a paywall</li>
|
||||
<li>Vendor lock-in</li>
|
||||
<li>Become a commercial entity (even indirectly)</li>
|
||||
<li>Anything that does not have an Open Source standard behind it</li>
|
||||
<li>Directly control a guest Operating System</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="m-2 px-4 pt-2 flex-col shadow-md
|
||||
rounded-2xl border-8 border-double divide-solid divide-y-2
|
||||
bg-dracula-900 text-dracula-100 shadow-dracula-900
|
||||
divide-dracula-800 border-dracula-800">
|
||||
<h3 class="text-xl font-semibold">Why?</h3>
|
||||
<ul class="list-image-informational list-inside leading-relaxed">
|
||||
<li>Broadcom buying VMWare, and VMWare losing a free teir for
|
||||
homelabbers pissed me off</li>
|
||||
<li>Vendor lock-in pisses me off</li>
|
||||
<li>Even good open source Hyperconverged systems (Proxmox, as
|
||||
an example) exhibit a form of vendor lock-in</li>
|
||||
<li>Libvirt is terrific, has the functionality for everything
|
||||
those other providers do, but there really is not a
|
||||
great option for those dipping their toes into Open Source</li>
|
||||
<li>Its fun to build things that solve a need</li>
|
||||
<li>I really want to do it</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="m-2 px-4 pt-2 flex-col shadow-md
|
||||
rounded-2xl border-8 border-double
|
||||
bg-voncount-900 text-voncount-100 shadow-voncount-900
|
||||
divide-voncount-800 border-voncount-800">
|
||||
<h3 class="text-xl font-semibold border-b-2 border-voncount-800">Other notes</h3>
|
||||
<p>I recently created a <a href="http://redd.it/1bct15z"
|
||||
class="after:content-link after:w-3 after:h-3 after:inline-block
|
||||
after:invert text-base text-morbius-100">post</a>
|
||||
on reddit announcing that I was building this, and while the majority
|
||||
of responses were supportive, even offering features that may enhance
|
||||
what I originally set out to do, many responded with
|
||||
"Why do we need another one??"</p>
|
||||
|
||||
<p>Besides the list above about why this exists, I wanted to clarify
|
||||
a few things those individuals did not seeem to get: This is not
|
||||
a rebuild of Proxmox, Cloudstack, VMWare, Harvester or any of
|
||||
the other "Hyper-converged/Single-solution/turnkey/Operating System"
|
||||
offerings out there. This will not take over your base operating system
|
||||
on your machine, just act as a cluster manager and interface to access
|
||||
the existing libvirtd instances on those machines, nor will it prescribe
|
||||
a set of requirements that make it hard to move your own infrastructure
|
||||
around.</p>
|
||||
|
||||
<p>At the heart of this project is that I hate the enshitifiation of
|
||||
Open Source that has been going on, where its just another way to make
|
||||
money and control the eco system. RedHat tried to do it by locking
|
||||
down their source code, Proxmox does it by making sure anything you
|
||||
do on Proxmox is tied to Proxmox (no offense to Proxmox), and even
|
||||
Hashicorp, who I loved so dearly, changed from a pure Open Source
|
||||
licensing model to one that protects the business over the community.</p>
|
||||
|
||||
<p>I will not let that happen here.</p>
|
||||
|
||||
<p>This project will seek to use the Unix philosophy, of building off
|
||||
of existing standards, combining tools, and having one tool do one
|
||||
job well. This does not mean there will be one application for each
|
||||
aspect of the job, but that this application stack will manage Libvirtd
|
||||
well, and have individual and configurable paths to manage each sub-aspect
|
||||
of the libvirt stack. This stack will not create a Ceph cluster for you,
|
||||
it leaves you to do that. It will not even talk to a ceph cluster.
|
||||
It will, however, let you add that cluster via configuration options to
|
||||
define it as a storage pool that libvirt can use.</p>
|
||||
|
||||
<p>If you want something that will allow you to use a single interface
|
||||
to create all sub aspects that can be used by libvirt
|
||||
(managing all firewall rules, creating a ceph cluster, etc.),
|
||||
use something like Proxmox which includes that builtin
|
||||
functionality. This isn't the stack for you.<p>
|
||||
</div>
|
||||
{{ end }}
|
257
view/static/home.templ
Normal file
@ -0,0 +1,257 @@
|
||||
package static
|
||||
|
||||
import (
|
||||
"git.staur.ca/stobbsm/clustvirt/view/layouts"
|
||||
"git.staur.ca/stobbsm/clustvirt/view/components"
|
||||
)
|
||||
|
||||
templ Home() {
|
||||
@layouts.StaticPage("ClustVirt", "Libvirt, clustered and managed", []components.NavItem{
|
||||
{Name: "What", Href: "#"},
|
||||
{Name: "Why", Href: "#"},
|
||||
{Name: "Goals", Href: "#"},
|
||||
{Name: "Stretch", Href: "#"},
|
||||
{Name: "Requests", Href: "#"},
|
||||
{Name: "Never", Href: "#"},
|
||||
{Name: "Notes", Href: "#"},
|
||||
}) {
|
||||
<div>
|
||||
@components.InfoBox("What is This?", "uiorange") {
|
||||
<div class={"mx-4"}>
|
||||
@components.ContentP() {
|
||||
Clustvirt (work in progress name) aims to be the agnostic
|
||||
cluster controller for libvirtd. The server component is
|
||||
used to display both the WebUI and run the REST API used to
|
||||
control one to many libvirtd hosts to manage virual machines,
|
||||
LXC containers (through libvirtd), gather information about
|
||||
each host, and monitor each host.
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div>
|
||||
@components.InfoBox("Why?", "uiblue") {
|
||||
@components.List("informational") {
|
||||
@components.ListItem() {
|
||||
Broadcom buying VMWare, and VMWare losing a free
|
||||
teir for homelabbers pissed me off
|
||||
}
|
||||
@components.ListItem() {
|
||||
Vendor lock-in pisses me off
|
||||
}
|
||||
@components.ListItem() {
|
||||
Even good open source Hyperconverged systems
|
||||
(Proxmox, as an example) exhibit a form of vendor lock-in
|
||||
}
|
||||
@components.ListItem() {
|
||||
Libvirt is terrific, has the functionality for everything
|
||||
those other providers do, but there really is not a great
|
||||
option for those dipping their toes into Open Source
|
||||
}
|
||||
@components.ListItem() {
|
||||
Its fun to build things that solve a need
|
||||
}
|
||||
@components.ListItem() {
|
||||
I really want to do it
|
||||
}
|
||||
}
|
||||
}
|
||||
</div>
|
||||
<div>
|
||||
@components.InfoBox("Project Goals", "uigreen") {
|
||||
@components.List("accepted") {
|
||||
@components.ListItem() {
|
||||
Open source, currently on the MIT license
|
||||
}
|
||||
@components.ListItem() {
|
||||
Base OS Agnostic. If it can run libvirtd, this should be able to control it on some level
|
||||
}
|
||||
@components.ListItem() {
|
||||
Control the Virtual Machine life cycle on one or more libvirtd hosts
|
||||
}
|
||||
@components.ListItem() {
|
||||
Add clusting capabilities to libvirtd host, including;
|
||||
}
|
||||
@components.ListItem() {
|
||||
Migration of VMs
|
||||
}
|
||||
@components.ListItem() {
|
||||
Syncronizing secrets
|
||||
}
|
||||
@components.ListItem() {
|
||||
Syncronizing VLANs, bridges, host only networking
|
||||
}
|
||||
@components.ListItem() {
|
||||
Sharing HA storage availability
|
||||
}
|
||||
@components.ListItem() {
|
||||
Locking shared resources like disks
|
||||
}
|
||||
@components.ListItem() {
|
||||
Starting VMs marked for HA on another host when one goes down
|
||||
}
|
||||
@components.ListItem() {
|
||||
Manage a library of Cloud-init resources and templates to build new VMs quickly
|
||||
}
|
||||
@components.ListItem() {
|
||||
Local Storage management, including local directory, lvm, zfs (if installed)
|
||||
}
|
||||
@components.ListItem() {
|
||||
Advanced Storage management, such as Ceph, glusterfs, drbd, iscsi, nfs
|
||||
}
|
||||
@components.ListItem() {
|
||||
Storage syncronization of local disks between hosts (zfs snapshots, lvm snapshots, rsync)
|
||||
}
|
||||
@components.ListItem() {
|
||||
Backup scheduling, creation, restoration
|
||||
}
|
||||
}
|
||||
}
|
||||
</div>
|
||||
<div>
|
||||
@components.InfoBox("Stretch Goals", "uipurple") {
|
||||
@components.List("possible") {
|
||||
@components.ListItem() {
|
||||
Install the OS which libvirtd is running on
|
||||
}
|
||||
@components.ListItem() {
|
||||
Install/provision libvirtd on a host that does not have it installed
|
||||
}
|
||||
@components.ListItem() {
|
||||
Tools to move from one vendor to clustvirt/libvirtd
|
||||
}
|
||||
@components.ListItem() {
|
||||
VM templates for common aspects of VM creation and management,
|
||||
like appliances
|
||||
}
|
||||
@components.ListItem() {
|
||||
External tool access that can be used to manage things that are not
|
||||
managed here (cephadm dashboard, for instance)
|
||||
}
|
||||
}
|
||||
}
|
||||
</div>
|
||||
<div>
|
||||
@components.InfoBox("Reddit Requested Feature", "uiyellow") {
|
||||
@components.List("possible") {
|
||||
@components.ListItem() {
|
||||
Search/Filter on hosts/vms - @Lopsided_Speaker_553
|
||||
}
|
||||
@components.ListItem() {
|
||||
Balance on resource usage per host/Automattically migrate to least
|
||||
used host - @Lopsided_Speaker_553
|
||||
}
|
||||
@components.ListItem() {
|
||||
Support inter-vm only commmunication (VxLAN style) - @Lopsided_Speaker_553
|
||||
}
|
||||
@components.ListItem() {
|
||||
Deploy VMs using only API - @Lopsided_Speaker_553
|
||||
}
|
||||
@components.ListItem() {
|
||||
Well documented, first class API - @kasperlitheater
|
||||
}
|
||||
@components.ListItem() {
|
||||
Bootstrap service to configure a new server - @phatpappa_
|
||||
}
|
||||
@components.ListItem() {
|
||||
For the love of kitten, don't use XML as configuration files - @pascalbrax
|
||||
}
|
||||
@components.ListItem() {
|
||||
Expose the Cluster Manager functionalities as API - @raven2611
|
||||
}
|
||||
@components.ListItem() {
|
||||
CPU architecture awareness for migrations - @raven2611
|
||||
}
|
||||
@components.ListItem() {
|
||||
Inter VM Communications via VXLAN/EVPN - @raven2611
|
||||
}
|
||||
}
|
||||
}
|
||||
</div>
|
||||
<div class={ }>
|
||||
@components.InfoBox("Never Going to Happen", "uired") {
|
||||
@components.List("never") {
|
||||
@components.ListItem() {
|
||||
Kubernetes
|
||||
}
|
||||
@components.ListItem() {
|
||||
Application container management (docker, podman, etc)
|
||||
}
|
||||
@components.ListItem() {
|
||||
Become an OS
|
||||
}
|
||||
@components.ListItem() {
|
||||
Have a paywall
|
||||
}
|
||||
@components.ListItem() {
|
||||
Vendor lock-in
|
||||
}
|
||||
@components.ListItem() {
|
||||
Become a commercial entity (even indirectly)
|
||||
}
|
||||
@components.ListItem() {
|
||||
Anything that does not have an Open Source standard behind it
|
||||
}
|
||||
@components.ListItem() {
|
||||
Directly control a guest Operating System
|
||||
}
|
||||
}
|
||||
}
|
||||
</div>
|
||||
@components.InfoBox("Other things to note", "uipink") {
|
||||
<div class={"flex", "flex-col", "gap-4", "mx-4"}>
|
||||
@components.ContentP() {
|
||||
I recently created a
|
||||
<span>
|
||||
@components.ANewTab("http://redd.it/1bct15z", "post")
|
||||
</span>
|
||||
on reddit announcing that I was building this, and while the majority
|
||||
of responses were supportive, even offering features that may enhance
|
||||
what I originally set out to do, many responded with
|
||||
"Why do we need another one??"
|
||||
}
|
||||
@components.ContentP() {
|
||||
Besides the list above about why this exists, I wanted to clarify
|
||||
a few things those individuals did not seeem to get: This is not
|
||||
a rebuild of Proxmox, Cloudstack, VMWare, Harvester or any of
|
||||
the other "Hyper-converged/Single-solution/turnkey/Operating System"
|
||||
offerings out there. This will not take over your base operating system
|
||||
on your machine, just act as a cluster manager and interface to access
|
||||
the existing libvirtd instances on those machines, nor will it prescribe
|
||||
a set of requirements that make it hard to move your own infrastructure
|
||||
around.
|
||||
}
|
||||
@components.ContentP() {
|
||||
At the heart of this project is that I hate the enshitifiation of
|
||||
Open Source that has been going on, where its just another way to make
|
||||
money and control the eco system. RedHat tried to do it by locking
|
||||
down their source code, Proxmox does it by making sure anything you
|
||||
do on Proxmox is tied to Proxmox (no offense to Proxmox), and even
|
||||
Hashicorp, who I loved so dearly, changed from a pure Open Source
|
||||
licensing model to one that protects the business over the community
|
||||
}
|
||||
@components.ContentP() {
|
||||
I will not let that happen here.
|
||||
}
|
||||
@components.ContentP() {
|
||||
This project will seek to use the Unix philosophy, of building off
|
||||
of existing standards, combining tools, and having one tool do one
|
||||
job well. This does not mean there will be one application for each
|
||||
aspect of the job, but that this application stack will manage Libvirtd
|
||||
well, and have individual and configurable paths to manage each sub-aspect
|
||||
of the libvirt stack. This stack will not create a Ceph cluster for you,
|
||||
it leaves you to do that. It will not even talk to a ceph cluster.
|
||||
It will, however, let you add that cluster via configuration options to
|
||||
define it as a storage pool that libvirt can use.
|
||||
}
|
||||
@components.ContentP() {
|
||||
If you want something that will allow you to use a single interface
|
||||
to create all sub aspects that can be used by libvirt
|
||||
(managing all firewall rules, creating a ceph cluster, etc.),
|
||||
use something like Proxmox which includes that builtin
|
||||
functionality. This isn't the stack for you.
|
||||
}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
1459
view/static/home_templ.go
Normal file
55
view/view.go
@ -1,55 +0,0 @@
|
||||
// Package view handles WebUI generation for clustvirt. The methods and utilties in this module control what is viewed,
|
||||
// templates that are loaded, and building those templates. Caching is not considered beyond what is done
|
||||
// automattically by go (if anything).
|
||||
package view
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
// View is responsible for assembling a group of templates, providing
|
||||
// methods to add data and compose pages in a common way.
|
||||
type View struct {
|
||||
content string
|
||||
template *template.Template
|
||||
}
|
||||
|
||||
var basetemplate *template.Template
|
||||
|
||||
// New returns a new instance of the View, expecting the content to be the actual
|
||||
// content as a template defining "content", in string format.
|
||||
func New(content string) *View {
|
||||
if basetemplate == nil {
|
||||
log.Println("Initializing base template")
|
||||
basetemplate = template.Must(template.New("").ParseFiles(index, header, footer))
|
||||
}
|
||||
log.Println("Cloning base template")
|
||||
v := &View{template: template.Must(basetemplate.Clone())}
|
||||
v.parse(content)
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// NewFromFile loads a template from a file
|
||||
func NewFromFile(file string) (*View, error) {
|
||||
b, err := os.ReadFile(file)
|
||||
return New(string(b)), err
|
||||
}
|
||||
|
||||
func (v *View) parse(tmpl string) error {
|
||||
log.Println("Parsing template contents")
|
||||
if _, err := v.template.Parse(tmpl); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Println("Template parsed")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Render returns the executed template with data
|
||||
func (v *View) Render(w io.Writer, data any) error {
|
||||
log.Println("Excuting template")
|
||||
return v.template.ExecuteTemplate(w, "_index", data)
|
||||
}
|