From 1fd6b1d36d099a3301b62293d004e26bea83a070 Mon Sep 17 00:00:00 2001 From: Jeff Carr Date: Mon, 24 Mar 2025 21:53:31 -0500 Subject: [PATCH] continued work on the GUI --- argv.go | 26 +----------- doAdminGui.go | 106 +++++++++++++++++++++++++++++++++++----------- doGui.go | 11 ----- main.go | 36 +++++++++++++++- structs.go | 8 +++- windowDroplets.go | 2 +- 6 files changed, 126 insertions(+), 63 deletions(-) diff --git a/argv.go b/argv.go index 70adb00..b3cab5c 100644 --- a/argv.go +++ b/argv.go @@ -16,36 +16,16 @@ type args struct { 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"` 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 { return ` - virtigo will help control your cluster + virtigo: control your cluster This maintains a master list of all your vm's (aka 'droplets') in your homelab cloud. You can import libvirt xml files. 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 POLL *log.LogFlag var WARN *log.LogFlag -var SPEW *log.LogFlag var EVENT *log.LogFlag func init() { @@ -66,6 +45,5 @@ func init() { INFO = log.NewFlag("INFO", false, full, short, "general virtigo") POLL = log.NewFlag("POLL", false, full, short, "virtigo polling") 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") } diff --git a/doAdminGui.go b/doAdminGui.go index e7cdf48..11d02f0 100644 --- a/doAdminGui.go +++ b/doAdminGui.go @@ -10,6 +10,7 @@ import ( "fmt" "io/ioutil" "net/http" + "net/url" "os" "os/user" "time" @@ -26,11 +27,15 @@ func (admin *adminT) refresh() { log.Info("virtigo scan here") } - url := argv.Server + if admin.url == nil { + log.Info("admin url == nil") + return + } + msg := []byte(`{"message": "Hello"}`) // 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) } else { log.Info("Response:", string(data)) @@ -38,7 +43,7 @@ func (admin *adminT) refresh() { } // 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) } else { fmt.Println("DropletsPB Response len:", len(data)) @@ -51,7 +56,7 @@ func (admin *adminT) refresh() { } // 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) } else { fmt.Println("HypervisorsPB Response len:", len(data)) @@ -64,7 +69,7 @@ func (admin *adminT) refresh() { } // 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) } else { fmt.Println("EventsPB Response len:", len(data)) @@ -92,11 +97,12 @@ func (admin *adminT) doAdminGui() { 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() { log.Warn("Main window close") os.Exit(0) } + me.gwin = win admin.uptime = win.Group.NewLabel("uptime") @@ -161,6 +167,14 @@ func (admin *adminT) doAdminGui() { // 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 for { 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) { req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(data)) if err != nil { @@ -191,22 +266,3 @@ func postData(url string, data []byte) ([]byte, error) { 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)) - } - } -} -*/ diff --git a/doGui.go b/doGui.go index 3e6b2ba..13bba73 100644 --- a/doGui.go +++ b/doGui.go @@ -45,17 +45,6 @@ func doGui() { func drawWindow(win *gadgets.GenericWindow) { 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 grid.NewButton("show hypervisors", func() { if newHyperWin != nil { diff --git a/main.go b/main.go index e34750b..b6403fc 100644 --- a/main.go +++ b/main.go @@ -5,10 +5,12 @@ package main import ( "embed" "fmt" + "net/url" "os" "path/filepath" "time" + "github.com/google/uuid" "go.wit.com/dev/alexflint/arg" "go.wit.com/gui" "go.wit.com/lib/protobuf/virtpb" @@ -37,12 +39,44 @@ func main() { 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.doAdminGui() 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 me.unstable = time.Now() // initialize the grid as unstable me.changed = false diff --git a/structs.go b/structs.go index e5cb9c0..c7010f9 100644 --- a/structs.go +++ b/structs.go @@ -1,9 +1,11 @@ package main import ( + "net/url" "time" "go.wit.com/gui" + "go.wit.com/lib/gadgets" "go.wit.com/lib/protobuf/virtpb" ) @@ -21,7 +23,7 @@ func (b *virtigoT) Enable() { // this app's variables type virtigoT struct { - cluster *virtpb.Cluster // basic cluster settings + cluster *virtpb.OldCluster // basic cluster settings myGui *gui.Node // the gui toolkit handle e *virtpb.Events // virt protobuf events hmap map[*virtpb.Hypervisor]*HyperT // map to the local struct @@ -37,6 +39,9 @@ type virtigoT struct { status *gui.Node // the cluster status lastuptime *gui.Node // the last time uptime was checked by Kuma 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 { @@ -48,6 +53,7 @@ type adminT struct { dwin *stdDropletTableWin // the droplet window hwin *stdHypervisorTableWin // the hypervisor window ewin *stdEventTableWin // the events window + url *url.URL // URL for the cloud } // the stuff that is needed for a hypervisor diff --git a/windowDroplets.go b/windowDroplets.go index 09c2d18..2c7aa81 100644 --- a/windowDroplets.go +++ b/windowDroplets.go @@ -161,7 +161,7 @@ func (dw *stdDropletTableWin) doInactiveDroplets(pb *virtpb.Droplets) { 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 }) vp.Custom = func(d *virtpb.Droplet) {