continued work on the GUI

This commit is contained in:
Jeff Carr 2025-03-24 21:53:31 -05:00
parent a7e639cdb0
commit 1fd6b1d36d
6 changed files with 126 additions and 63 deletions

26
argv.go
View File

@ -16,36 +16,16 @@ type args struct {
Port int `arg:"--port" default:"8080" help:"allow droplet events via http"` Port int `arg:"--port" default:"8080" help:"allow droplet events via http"`
Server string `arg:"env:VIRTIGO_SERVER" help:"what virtigo cluster to connect to"` Server string `arg:"env:VIRTIGO_SERVER" help:"what virtigo cluster to connect to"`
Xml []string `arg:"--libvirt" help:"import qemu xml files: --libvirt /etc/libvirt/qemu/*.xml"` Xml []string `arg:"--libvirt" help:"import qemu xml files: --libvirt /etc/libvirt/qemu/*.xml"`
Admin bool `arg:"--admin" help:"enter admin mode"`
} }
// Daemon bool `arg:"--daemon" help:"run in daemon mode"`
// IgnoreCpu bool `arg:"--xml-ignore-cpu" default:"true" help:"ignore non-standard libvirt xml cpus"`
// IgnoreBr bool `arg:"--xml-ignore-net" default:"true" help:"ignore network bridge name changes"`
// IgnDisk bool `arg:"--xml-ignore-disk" default:"false" help:"ignore duplicate disk names"`
// Save bool `arg:"--save" default:"false" help:"save protobuf config after import"`
// Start string `arg:"--start" help:"start a droplet"`
// Uptime bool `arg:"--uptime" default:"true" help:"allow uptime checks for things like Kuma"`
// Hosts []string `arg:"--hosts" help:"hosts to connect to"`
func (a args) Description() string { func (a args) Description() string {
return ` return `
virtigo will help control your cluster virtigo: control your cluster
This maintains a master list of all your vm's (aka 'droplets') This maintains a master list of all your vm's (aka 'droplets')
in your homelab cloud. You can import libvirt xml files. in your homelab cloud. You can import libvirt xml files.
This app talks to your hypervisors via the virtigod daemon. This app talks to your hypervisors via the virtigod daemon.
At this time, this _only_ supports qcow2 images. If you need
something else you'll have to add it in virtigolib.
This runs a http server so you can control your virtual machines.
For example to start a vm called 'www.wit.com' your cluster 'foo.bar.com':
curl http://virtigo.foo.com/start?www.wit.com
Use 'virtigoctl' to import xml files from libvirt and configure new
hypervisors in your cluster.
` `
} }
@ -56,7 +36,6 @@ func (args) Version() string {
var INFO *log.LogFlag var INFO *log.LogFlag
var POLL *log.LogFlag var POLL *log.LogFlag
var WARN *log.LogFlag var WARN *log.LogFlag
var SPEW *log.LogFlag
var EVENT *log.LogFlag var EVENT *log.LogFlag
func init() { func init() {
@ -66,6 +45,5 @@ func init() {
INFO = log.NewFlag("INFO", false, full, short, "general virtigo") INFO = log.NewFlag("INFO", false, full, short, "general virtigo")
POLL = log.NewFlag("POLL", false, full, short, "virtigo polling") POLL = log.NewFlag("POLL", false, full, short, "virtigo polling")
WARN = log.NewFlag("WARN", true, full, short, "bad things") WARN = log.NewFlag("WARN", true, full, short, "bad things")
SPEW = log.NewFlag("SPEW", true, full, short, "dump everything")
EVENT = log.NewFlag("EVENT", true, full, short, "hypeprvisor/droplet events") EVENT = log.NewFlag("EVENT", true, full, short, "hypeprvisor/droplet events")
} }

View File

@ -10,6 +10,7 @@ import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"net/url"
"os" "os"
"os/user" "os/user"
"time" "time"
@ -26,11 +27,15 @@ func (admin *adminT) refresh() {
log.Info("virtigo scan here") log.Info("virtigo scan here")
} }
url := argv.Server if admin.url == nil {
log.Info("admin url == nil")
return
}
msg := []byte(`{"message": "Hello"}`) msg := []byte(`{"message": "Hello"}`)
// display the uptime // display the uptime
if data, err := postData(url+"/uptime", msg); err != nil { if data, err := postData(admin.url.String()+"/uptime", msg); err != nil {
log.Info("/uptime Error:", err) log.Info("/uptime Error:", err)
} else { } else {
log.Info("Response:", string(data)) log.Info("Response:", string(data))
@ -38,7 +43,7 @@ func (admin *adminT) refresh() {
} }
// update the droplet list // update the droplet list
if data, err := postData(url+"/DropletsPB", msg); err != nil { if data, err := postData(admin.url.String()+"/DropletsPB", msg); err != nil {
log.Info("/DropletsPB Error:", err) log.Info("/DropletsPB Error:", err)
} else { } else {
fmt.Println("DropletsPB Response len:", len(data)) fmt.Println("DropletsPB Response len:", len(data))
@ -51,7 +56,7 @@ func (admin *adminT) refresh() {
} }
// update the hypervisor list // update the hypervisor list
if data, err := postData(url+"/HypervisorsPB", msg); err != nil { if data, err := postData(admin.url.String()+"/HypervisorsPB", msg); err != nil {
log.Info("Error:", err) log.Info("Error:", err)
} else { } else {
fmt.Println("HypervisorsPB Response len:", len(data)) fmt.Println("HypervisorsPB Response len:", len(data))
@ -64,7 +69,7 @@ func (admin *adminT) refresh() {
} }
// update the events list // update the events list
if data, err := postData(url+"/EventsPB", msg); err != nil { if data, err := postData(admin.url.String()+"/EventsPB", msg); err != nil {
log.Info("Error:", err) log.Info("Error:", err)
} else { } else {
fmt.Println("EventsPB Response len:", len(data)) fmt.Println("EventsPB Response len:", len(data))
@ -92,11 +97,12 @@ func (admin *adminT) doAdminGui() {
Timeout: 10 * time.Second, // Set a reasonable timeout Timeout: 10 * time.Second, // Set a reasonable timeout
} }
win := gadgets.NewGenericWindow("Virtigo: (run your cluster)", "virtigo stuff") win := gadgets.NewGenericWindow("Virtigo: (run your cluster)", "localhost")
win.Custom = func() { win.Custom = func() {
log.Warn("Main window close") log.Warn("Main window close")
os.Exit(0) os.Exit(0)
} }
me.gwin = win
admin.uptime = win.Group.NewLabel("uptime") admin.uptime = win.Group.NewLabel("uptime")
@ -161,6 +167,14 @@ func (admin *adminT) doAdminGui() {
// okExit("admin close") // okExit("admin close")
}) })
me.cmap = make(map[*virtpb.Cluster]*adminT)
for c := range me.clusters.IterAll() {
a := new(adminT)
me.cmap[c] = a
log.Info("found in the config file", c.URL)
a.makeClusterGroup(c)
}
// sit here forever refreshing the GUI // sit here forever refreshing the GUI
for { for {
admin.refresh() admin.refresh()
@ -168,6 +182,67 @@ func (admin *adminT) doAdminGui() {
} }
} }
func (admin *adminT) makeClusterGroup(c *virtpb.Cluster) {
var err error
admin.url, err = url.Parse(c.URL)
if err != nil {
badExit(err)
}
group := me.gwin.Bottom.NewGroup(admin.url.Hostname())
admin.uptime = group.NewLabel("uptime")
grid := group.RawGrid()
grid.NewButton("show hypervisors", func() {
if admin.hypervisors == nil {
log.Info("hypervisors not initialized")
return
}
log.Info("Hypervisors len=", admin.hypervisors.Len())
admin.hwin = newHypervisorsWindow()
admin.hwin.doStdHypervisors(admin.hypervisors)
admin.hwin.win.Custom = func() {
log.Info("hiding table window")
}
})
grid.NewButton("droplets", func() {
if admin.droplets == nil {
log.Info("droplets not initialized")
return
}
admin.dwin = newDropletsWindow()
admin.dwin.win.Custom = func() {
log.Info("hiding droplet table window")
}
var found *virtpb.Droplets
found = virtpb.NewDroplets()
all := admin.droplets.All()
for all.Scan() {
vm := all.Next()
if vm.Current.State != virtpb.DropletState_ON {
continue
}
found.Append(vm)
}
admin.dwin.doActiveDroplets(found)
})
grid.NewButton("events", func() {
if admin.events == nil {
log.Info("events are not initialized")
return
}
log.Info("Events len=", admin.events.Len())
admin.ewin = newEventsWindow()
admin.ewin.doStdEvents(admin.events)
admin.ewin.win.Custom = func() {
log.Info("hiding table window")
}
})
}
func postData(url string, data []byte) ([]byte, error) { func postData(url string, data []byte) ([]byte, error) {
req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(data)) req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(data))
if err != nil { if err != nil {
@ -191,22 +266,3 @@ func postData(url string, data []byte) ([]byte, error) {
return body, nil return body, nil
} }
/*
func main() {
url := "http://example.com/endpoint"
data := []byte(`{"message": "Hello"}`)
ticker := time.NewTicker(1 * time.Minute)
defer ticker.Stop()
for range ticker.C {
response, err := postData(url, data)
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Println("Response:", string(response))
}
}
}
*/

View File

@ -45,17 +45,6 @@ func doGui() {
func drawWindow(win *gadgets.GenericWindow) { func drawWindow(win *gadgets.GenericWindow) {
grid := win.Group.RawGrid() grid := win.Group.RawGrid()
/*
var hyperWin *gadgets.GenericWindow
grid.NewButton("hypervisors", func() {
if hyperWin != nil {
hyperWin.Toggle()
return
}
hyperWin = makeHypervisorsWindow(me.cluster.H)
})
*/
var newHyperWin *stdHypervisorTableWin var newHyperWin *stdHypervisorTableWin
grid.NewButton("show hypervisors", func() { grid.NewButton("show hypervisors", func() {
if newHyperWin != nil { if newHyperWin != nil {

36
main.go
View File

@ -5,10 +5,12 @@ package main
import ( import (
"embed" "embed"
"fmt" "fmt"
"net/url"
"os" "os"
"path/filepath" "path/filepath"
"time" "time"
"github.com/google/uuid"
"go.wit.com/dev/alexflint/arg" "go.wit.com/dev/alexflint/arg"
"go.wit.com/gui" "go.wit.com/gui"
"go.wit.com/lib/protobuf/virtpb" "go.wit.com/lib/protobuf/virtpb"
@ -37,12 +39,44 @@ func main() {
os.Setenv("VIRTIGO_HOME", fullpath) os.Setenv("VIRTIGO_HOME", fullpath)
} }
if argv.Server != "" { me.clusters = virtpb.NewClusters()
if argv.Admin {
err := me.clusters.ConfigLoad()
if err != nil {
badExit(err)
}
me.admin = new(adminT) me.admin = new(adminT)
me.admin.doAdminGui() me.admin.doAdminGui()
okExit("admin close") okExit("admin close")
} }
if argv.Server != "" {
log.Info("start admin interface")
me.admin = new(adminT)
var err error
me.admin.url, err = url.Parse(argv.Server)
if err != nil {
badExit(err)
}
err = me.clusters.ConfigLoad()
if err != nil {
clusters := virtpb.NewClusters()
c := new(virtpb.Cluster)
c.Uuid = uuid.New().String()
c.URL = argv.Server
clusters.Append(c)
virtpb.ConfigWriteTEXT(clusters, "cluster.text")
badExit(err)
}
me.admin.doAdminGui()
okExit("admin close")
}
os.Exit(-1)
// set defaults // set defaults
me.unstable = time.Now() // initialize the grid as unstable me.unstable = time.Now() // initialize the grid as unstable
me.changed = false me.changed = false

View File

@ -1,9 +1,11 @@
package main package main
import ( import (
"net/url"
"time" "time"
"go.wit.com/gui" "go.wit.com/gui"
"go.wit.com/lib/gadgets"
"go.wit.com/lib/protobuf/virtpb" "go.wit.com/lib/protobuf/virtpb"
) )
@ -21,7 +23,7 @@ func (b *virtigoT) Enable() {
// this app's variables // this app's variables
type virtigoT struct { type virtigoT struct {
cluster *virtpb.Cluster // basic cluster settings cluster *virtpb.OldCluster // basic cluster settings
myGui *gui.Node // the gui toolkit handle myGui *gui.Node // the gui toolkit handle
e *virtpb.Events // virt protobuf events e *virtpb.Events // virt protobuf events
hmap map[*virtpb.Hypervisor]*HyperT // map to the local struct hmap map[*virtpb.Hypervisor]*HyperT // map to the local struct
@ -37,6 +39,9 @@ type virtigoT struct {
status *gui.Node // the cluster status status *gui.Node // the cluster status
lastuptime *gui.Node // the last time uptime was checked by Kuma lastuptime *gui.Node // the last time uptime was checked by Kuma
admin *adminT // the admin struct admin *adminT // the admin struct
clusters *virtpb.Clusters // clusters protobuf
cmap map[*virtpb.Cluster]*adminT // map to local GUI objects and the protobuf
gwin *gadgets.GenericWindow // main window
} }
type adminT struct { type adminT struct {
@ -48,6 +53,7 @@ type adminT struct {
dwin *stdDropletTableWin // the droplet window dwin *stdDropletTableWin // the droplet window
hwin *stdHypervisorTableWin // the hypervisor window hwin *stdHypervisorTableWin // the hypervisor window
ewin *stdEventTableWin // the events window ewin *stdEventTableWin // the events window
url *url.URL // URL for the cloud
} }
// the stuff that is needed for a hypervisor // the stuff that is needed for a hypervisor

View File

@ -161,7 +161,7 @@ func (dw *stdDropletTableWin) doInactiveDroplets(pb *virtpb.Droplets) {
return d.Current.Hypervisor return d.Current.Hypervisor
}) })
*/ */
vp := t.AddButtonFunc("Configure Hostname", func(p *virtpb.Droplet) string { vp := t.AddButtonFunc("Verify Config", func(p *virtpb.Droplet) string {
return p.Hostname return p.Hostname
}) })
vp.Custom = func(d *virtpb.Droplet) { vp.Custom = func(d *virtpb.Droplet) {