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]
|
[build]
|
||||||
args_bin = []
|
args_bin = []
|
||||||
bin = "./tmp/main"
|
bin = "./tmp/main"
|
||||||
cmd = "go build -o ./tmp/main ."
|
pre_cmd = [""]
|
||||||
|
cmd = "templ generate && go build -o ./tmp/main ."
|
||||||
delay = 1000
|
delay = 1000
|
||||||
exclude_dir = ["tmp", "vendor", "testdata", "assets/node_modules"]
|
exclude_dir = ["tmp", "vendor", "testdata", "node_modules", "public"]
|
||||||
exclude_file = []
|
exclude_file = []
|
||||||
exclude_regex = ["_test.go"]
|
exclude_regex = ["_test.go", "_templ.go"]
|
||||||
exclude_unchanged = false
|
exclude_unchanged = false
|
||||||
follow_symlink = false
|
follow_symlink = false
|
||||||
full_bin = ""
|
full_bin = ""
|
||||||
include_dir = []
|
include_dir = []
|
||||||
include_ext = ["go", "tpl", "tmpl", "html", "gohtml", "css"]
|
include_ext = ["go", "css", "templ", "config.js"]
|
||||||
include_file = []
|
include_file = []
|
||||||
kill_delay = "0s"
|
kill_delay = "0s"
|
||||||
log = "build-errors.log"
|
log = "build-errors.log"
|
||||||
poll = false
|
poll = false
|
||||||
poll_interval = 0
|
poll_interval = 0
|
||||||
post_cmd = []
|
post_cmd = []
|
||||||
pre_cmd = []
|
|
||||||
rerun = false
|
rerun = false
|
||||||
rerun_delay = 500
|
rerun_delay = 500
|
||||||
send_interrupt = false
|
send_interrupt = false
|
||||||
|
25
README.md
@ -72,3 +72,28 @@ Overall goals:
|
|||||||
</clustvirt:data>
|
</clustvirt:data>
|
||||||
</metadata>
|
</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
|
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 (
|
require (
|
||||||
github.com/go-chi/chi/v5 v5.0.12 // indirect
|
github.com/blend/go-sdk v1.20220411.3 // indirect
|
||||||
gopkg.in/ffmt.v1 v1.5.6 // 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 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s=
|
||||||
github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
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 h1:4Bu3riZp5sAIXW2T/18JM9BkwJLodurXFR0f7PXp+cw=
|
||||||
gopkg.in/ffmt.v1 v1.5.6/go.mod h1:LssvGOZFiBGoBcobkTqnyh+uN1VzIRoibW+c0JI/Ha4=
|
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 h1:lEVDNE7xfzmZXiDEGIS8NvJSuaz11OjRXw+ufbQEtPY=
|
||||||
libvirt.org/go/libvirt v1.10001.0/go.mod h1:1WiFE8EjZfq+FCVog+rvr1yatKbKZ9FaFMZgEqxEJqQ=
|
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/lib/storagevol"
|
||||||
"git.staur.ca/stobbsm/clustvirt/util"
|
"git.staur.ca/stobbsm/clustvirt/util"
|
||||||
"libvirt.org/go/libvirt"
|
"libvirt.org/go/libvirt"
|
||||||
|
"libvirt.org/go/libvirtxml"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Host holds information and acts as a connection handle for a Host
|
// Host holds information and acts as a connection handle for a Host
|
||||||
@ -80,6 +81,9 @@ type VMInfo struct {
|
|||||||
ID uint
|
ID uint
|
||||||
UUID []byte
|
UUID []byte
|
||||||
XML string
|
XML string
|
||||||
|
Active bool
|
||||||
|
VCPUs uint
|
||||||
|
Memory uint
|
||||||
// States are the current states active on the host
|
// States are the current states active on the host
|
||||||
States []guest.VMState
|
States []guest.VMState
|
||||||
}
|
}
|
||||||
@ -105,6 +109,7 @@ type StoragePoolInfo struct {
|
|||||||
Capacity uint64
|
Capacity uint64
|
||||||
Allocation uint64
|
Allocation uint64
|
||||||
Available uint64
|
Available uint64
|
||||||
|
IsNet bool
|
||||||
// HAEnabled indicates if the storage pool has High Availability
|
// HAEnabled indicates if the storage pool has High Availability
|
||||||
HAEnabled bool
|
HAEnabled bool
|
||||||
// Volumes defined in the storage pool
|
// Volumes defined in the storage pool
|
||||||
@ -113,13 +118,13 @@ type StoragePoolInfo struct {
|
|||||||
|
|
||||||
// VolumeInfo holds basic information about Volumes available in storage pools
|
// VolumeInfo holds basic information about Volumes available in storage pools
|
||||||
type VolumeInfo struct {
|
type VolumeInfo struct {
|
||||||
Name string
|
Name string
|
||||||
Key string
|
Key string
|
||||||
Path string
|
Path string
|
||||||
Type string
|
Type string
|
||||||
Capacity uint64
|
Capacity uint64
|
||||||
Allocation uint64
|
Allocation uint64
|
||||||
XML string
|
XML string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NetIfInfo holds basic information about available network interfaces (not their connections, the devices themselves)
|
// NetIfInfo holds basic information about available network interfaces (not their connections, the devices themselves)
|
||||||
@ -134,6 +139,7 @@ type NetworkInfo struct {
|
|||||||
Name string
|
Name string
|
||||||
UUID []byte
|
UUID []byte
|
||||||
XML string
|
XML string
|
||||||
|
Active bool
|
||||||
// NetIf is the network interface this connection is applied to
|
// NetIf is the network interface this connection is applied to
|
||||||
NetIf NetIfInfo
|
NetIf NetIfInfo
|
||||||
}
|
}
|
||||||
@ -228,29 +234,31 @@ func (h *Host) Close() error {
|
|||||||
// private methods that load the different informational parts
|
// private methods that load the different informational parts
|
||||||
|
|
||||||
func (h *Host) getInfo() {
|
func (h *Host) getInfo() {
|
||||||
var wg sync.WaitGroup
|
var wg = new(sync.WaitGroup)
|
||||||
|
|
||||||
infoFuncs := []func(*sync.WaitGroup){
|
infoFuncs := []func(){
|
||||||
h.getDevicesInfo,
|
h.getDevicesInfo,
|
||||||
h.getDomainInfo,
|
// h.getDomainInfo,
|
||||||
h.getIfaceInfo,
|
h.getIfaceInfo,
|
||||||
h.getNetsInfo,
|
h.getNetsInfo,
|
||||||
h.getNodeInfo,
|
h.getNodeInfo,
|
||||||
h.getSEVInfo,
|
// h.getSEVInfo,
|
||||||
h.getSecretsInfo,
|
// h.getSecretsInfo,
|
||||||
h.getStoragePools,
|
// h.getStoragePools,
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, f := range infoFuncs {
|
for _, f := range infoFuncs {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
f(&wg)
|
go func(wg *sync.WaitGroup) {
|
||||||
|
defer wg.Done()
|
||||||
|
f()
|
||||||
|
}(wg)
|
||||||
}
|
}
|
||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Host) getStoragePools(wg *sync.WaitGroup) {
|
func (h *Host) getStoragePools() {
|
||||||
defer wg.Done()
|
|
||||||
spools, err := h.conn.ListAllStoragePools(0)
|
spools, err := h.conn.ListAllStoragePools(0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
@ -285,6 +293,19 @@ func (h *Host) getStoragePools(wg *sync.WaitGroup) {
|
|||||||
h.StoragePoolList[i].Allocation = spInfo.Allocation
|
h.StoragePoolList[i].Allocation = spInfo.Allocation
|
||||||
h.StoragePoolList[i].Available = spInfo.Available
|
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)
|
svols, err := s.ListAllStorageVolumes(0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
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 {
|
if h.StoragePoolList[i].Volumes[j].Name, err = sv.GetName(); err != nil {
|
||||||
log.Println(err)
|
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)
|
log.Println(err)
|
||||||
}
|
}
|
||||||
if h.StoragePoolList[i].Volumes[j].Path, err = sv.GetPath(); err != nil {
|
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) {
|
func (h *Host) getSecretsInfo() {
|
||||||
defer wg.Done()
|
|
||||||
nsecrets, err := h.conn.ListAllSecrets(0)
|
nsecrets, err := h.conn.ListAllSecrets(0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
@ -344,8 +364,7 @@ func (h *Host) getSecretsInfo(wg *sync.WaitGroup) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Host) getNodeInfo(wg *sync.WaitGroup) {
|
func (h *Host) getNodeInfo() {
|
||||||
defer wg.Done()
|
|
||||||
var err error
|
var err error
|
||||||
h.AvailableCPUTypes, err = h.conn.GetCPUModelNames("x86_64", 0)
|
h.AvailableCPUTypes, err = h.conn.GetCPUModelNames("x86_64", 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -413,8 +432,7 @@ func (h *Host) getNodeInfo(wg *sync.WaitGroup) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Host) getSEVInfo(wg *sync.WaitGroup) {
|
func (h *Host) getSEVInfo() {
|
||||||
defer wg.Done()
|
|
||||||
// getSEVInfo
|
// getSEVInfo
|
||||||
h.HostSEVInfo.SEVEnabled = true
|
h.HostSEVInfo.SEVEnabled = true
|
||||||
ns, err := h.conn.GetSEVInfo(0)
|
ns, err := h.conn.GetSEVInfo(0)
|
||||||
@ -442,8 +460,7 @@ func (h *Host) getSEVInfo(wg *sync.WaitGroup) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Host) getDomainInfo(wg *sync.WaitGroup) {
|
func (h *Host) getDomainInfo() {
|
||||||
defer wg.Done()
|
|
||||||
// getDomainInfo
|
// getDomainInfo
|
||||||
doms, err := h.conn.ListAllDomains(0)
|
doms, err := h.conn.ListAllDomains(0)
|
||||||
if err != nil {
|
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 {
|
if h.VMList[i].XML, err = d.GetXMLDesc(0); err != nil {
|
||||||
log.Println(err)
|
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()
|
d.Free()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Host) getIfaceInfo(wg *sync.WaitGroup) {
|
func (h *Host) getIfaceInfo() {
|
||||||
defer wg.Done()
|
|
||||||
// getIfaceInfo
|
// getIfaceInfo
|
||||||
ifaces, err := h.conn.ListInterfaces()
|
ifaces, err := h.conn.ListInterfaces()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -498,8 +523,7 @@ func (h *Host) getIfaceInfo(wg *sync.WaitGroup) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Host) getNetsInfo(wg *sync.WaitGroup) {
|
func (h *Host) getNetsInfo() {
|
||||||
defer wg.Done()
|
|
||||||
// getNetsInfo
|
// getNetsInfo
|
||||||
nets, err := h.conn.ListNetworks()
|
nets, err := h.conn.ListNetworks()
|
||||||
if err != nil {
|
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 {
|
if h.NetworkList[i].XML, err = net.GetXMLDesc(0); err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
}
|
}
|
||||||
|
if h.NetworkList[i].Active, err = net.IsActive(); err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
net.Free()
|
net.Free()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Host) getDevicesInfo(wg *sync.WaitGroup) {
|
func (h *Host) getDevicesInfo() {
|
||||||
defer wg.Done()
|
|
||||||
ndevs, err := h.conn.ListAllNodeDevices(0)
|
ndevs, err := h.conn.ListAllNodeDevices(0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
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
|
package network
|
||||||
|
|
||||||
|
type Network struct{}
|
||||||
|
@ -9,3 +9,38 @@ var StoragePoolStateMap = map[libvirt.StoragePoolState]string{
|
|||||||
libvirt.STORAGE_POOL_DEGRADED: "degraded",
|
libvirt.STORAGE_POOL_DEGRADED: "degraded",
|
||||||
libvirt.STORAGE_POOL_INACCESSIBLE: "inaccessible",
|
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
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"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"
|
||||||
|
"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"
|
||||||
"github.com/go-chi/chi/v5/middleware"
|
"github.com/go-chi/chi/v5/middleware"
|
||||||
)
|
)
|
||||||
@ -13,42 +20,63 @@ const DEBUG bool = true
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
log.Println("Starting clustvirt, the libvirt cluster manager")
|
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")
|
initStats := cluster.InitStats(clst)
|
||||||
//if err != nil {
|
|
||||||
// log.Fatal(err)
|
|
||||||
//}
|
|
||||||
//defer venus.Close()
|
|
||||||
//lm, err := venus.GetGuestByName("logan-minecraft")
|
|
||||||
//if err != nil {
|
|
||||||
// log.Fatal(err)
|
|
||||||
//}
|
|
||||||
//defer lm.Close()
|
|
||||||
|
|
||||||
// Start webserver and serve homepage
|
// 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")))
|
fs := http.StripPrefix("/static/", http.FileServer(http.Dir("public")))
|
||||||
|
|
||||||
r := chi.NewRouter()
|
r := chi.NewRouter()
|
||||||
r.Use(middleware.Logger)
|
r.Use(middleware.Logger)
|
||||||
|
if DEBUG {
|
||||||
|
r.Use(middleware.NoCache)
|
||||||
|
}
|
||||||
r.Get("/static/*", func(w http.ResponseWriter, r *http.Request) {
|
r.Get("/static/*", func(w http.ResponseWriter, r *http.Request) {
|
||||||
defer r.Body.Close()
|
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)
|
fs.ServeHTTP(w, r)
|
||||||
})
|
})
|
||||||
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
r.Get("/", templ.Handler(view.HostMain(defaultNavBar)).ServeHTTP)
|
||||||
defer r.Body.Close()
|
r.Get("/cluster", func(w http.ResponseWriter, r *http.Request) {
|
||||||
if DEBUG {
|
initStats.Update()
|
||||||
w.Header().Add("Cache-Control", "no-cache, no-store, must-revalidate")
|
log.Println(
|
||||||
w.Header().Add("Pragma", "no-cache")
|
"Rendering clusterstats",
|
||||||
w.Header().Add("Expire", "0")
|
view.ClusterInfo(
|
||||||
}
|
initStats,
|
||||||
w.Header().Add("Content", "text/html")
|
initStats.Diff(),
|
||||||
log.Println(view.ViewHome.Render(w, nil))
|
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))
|
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,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/forms": "^0.5.7",
|
"@tailwindcss/forms": "^0.5.7",
|
||||||
"tailwind-dracula": "^1.1.0",
|
|
||||||
"tailwindcss": "^3.4.1"
|
"tailwindcss": "^3.4.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -200,12 +199,15 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/binary-extensions": {
|
"node_modules/binary-extensions": {
|
||||||
"version": "2.2.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
|
||||||
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
|
"integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/brace-expansion": {
|
"node_modules/brace-expansion": {
|
||||||
@ -1195,15 +1197,6 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"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": {
|
"node_modules/tailwindcss": {
|
||||||
"version": "3.4.1",
|
"version": "3.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.1.tgz",
|
@ -1,7 +1,6 @@
|
|||||||
{
|
{
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/forms": "^0.5.7",
|
"@tailwindcss/forms": "^0.5.7",
|
||||||
"tailwind-dracula": "^1.1.0",
|
|
||||||
"tailwindcss": "^3.4.1"
|
"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
|
<path
|
||||||
stroke-linecap="round"
|
stroke-linecap="round"
|
||||||
stroke-linejoin="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"
|
id="path1"
|
||||||
style="stroke-width:1.84615;stroke:#d6d6d6;stroke-opacity:1" />
|
style="stroke:#d6d6d6;stroke-width:1.53846;stroke-opacity:1" />
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
@ -29,16 +29,16 @@
|
|||||||
inkscape:zoom="9.8333333"
|
inkscape:zoom="9.8333333"
|
||||||
inkscape:cx="9.7627119"
|
inkscape:cx="9.7627119"
|
||||||
inkscape:cy="9.7627119"
|
inkscape:cy="9.7627119"
|
||||||
inkscape:window-width="1312"
|
inkscape:window-width="1440"
|
||||||
inkscape:window-height="449"
|
inkscape:window-height="847"
|
||||||
inkscape:window-x="0"
|
inkscape:window-x="0"
|
||||||
inkscape:window-y="25"
|
inkscape:window-y="25"
|
||||||
inkscape:window-maximized="0"
|
inkscape:window-maximized="1"
|
||||||
inkscape:current-layer="svg1" />
|
inkscape:current-layer="svg1" />
|
||||||
<path
|
<path
|
||||||
stroke-linecap="round"
|
stroke-linecap="round"
|
||||||
stroke-linejoin="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"
|
id="path1"
|
||||||
style="stroke:#d6d6d6;stroke-width:1.84615;stroke-opacity:1" />
|
style="stroke:#d6d6d6;stroke-width:1.53846;stroke-opacity:1" />
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
@ -38,7 +38,7 @@
|
|||||||
<path
|
<path
|
||||||
stroke-linecap="round"
|
stroke-linecap="round"
|
||||||
stroke-linejoin="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"
|
id="path1"
|
||||||
style="stroke:#d6d6d6;stroke-width:1.84462;stroke-opacity:1" />
|
style="stroke:#d6d6d6;stroke-width:1.53913;stroke-opacity:1" />
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
@ -38,7 +38,7 @@
|
|||||||
<path
|
<path
|
||||||
stroke-linecap="round"
|
stroke-linecap="round"
|
||||||
stroke-linejoin="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"
|
id="path1"
|
||||||
style="stroke:#d6d6d6;stroke-width:1.84615;stroke-opacity:1" />
|
style="stroke:#d6d6d6;stroke-width:1.53846;stroke-opacity:1" />
|
||||||
</svg>
|
</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)
|
|
||||||
}
|
|