diff --git a/go.mod b/go.mod index c8f1185..da184f8 100644 --- a/go.mod +++ b/go.mod @@ -7,3 +7,9 @@ require ( github.com/go-chi/chi/v5 v5.0.12 libvirt.org/go/libvirt v1.10001.0 ) + +require ( + github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect + github.com/wcharczuk/go-chart/v2 v2.1.1 // indirect + golang.org/x/image v0.11.0 // indirect +) diff --git a/go.sum b/go.sum index b275a5c..3455de0 100644 --- a/go.sum +++ b/go.sum @@ -2,7 +2,44 @@ 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/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s= github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/wcharczuk/go-chart/v2 v2.1.1 h1:2u7na789qiD5WzccZsFz4MJWOJP72G+2kUuJoSNqWnE= +github.com/wcharczuk/go-chart/v2 v2.1.1/go.mod h1:CyCAUt2oqvfhCl6Q5ZvAZwItgpQKZOkCJGb+VGv6l14= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/image v0.11.0 h1:ds2RoQvBvYTiJkwpSFDwCcDFNX7DqjL2WsUgTNk0Ooo= +golang.org/x/image v0.11.0/go.mod h1:bglhjqbqVuEb9e9+eNR45Jfu7D+T4Qan+NhQk8Ck2P8= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= libvirt.org/go/libvirt v1.10001.0 h1:lEVDNE7xfzmZXiDEGIS8NvJSuaz11OjRXw+ufbQEtPY= libvirt.org/go/libvirt v1.10001.0/go.mod h1:1WiFE8EjZfq+FCVog+rvr1yatKbKZ9FaFMZgEqxEJqQ= diff --git a/lib/host/charts.go b/lib/host/charts.go new file mode 100644 index 0000000..7f37068 --- /dev/null +++ b/lib/host/charts.go @@ -0,0 +1,25 @@ +package host + +import ( + "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 { + c := chart.PieChart{ + Width: 256, + Height: 256, + Values: []chart.Value{ + {Value: float64(h.NodeMemory.Free / h.NodeMemory.Total), Label: "Free"}, + {Value: float64(h.NodeMemory.Cached / h.NodeMemory.Total), Label: "Cached"}, + {Value: float64(h.NodeMemory.Buffers / h.NodeMemory.Total), Label: "Buffers"}, + }, + } + sb := new(strings.Builder) + c.Render(chart.SVG, sb) + return sb.String() +} diff --git a/main.go b/main.go index ab12e4d..2d9e3de 100644 --- a/main.go +++ b/main.go @@ -4,6 +4,7 @@ import ( "log" "net/http" + "git.staur.ca/stobbsm/clustvirt/lib/host" "git.staur.ca/stobbsm/clustvirt/view/components" "git.staur.ca/stobbsm/clustvirt/view/layouts" "git.staur.ca/stobbsm/clustvirt/view/static" @@ -17,11 +18,12 @@ const DEBUG bool = true func main() { log.Println("Starting clustvirt, the libvirt cluster manager") - //venus, err := host.ConnectHost(host.URI_QEMU_SSH_SYSTEM, "venus.staur.ca") - //if err != nil { - // log.Fatal(err) - //} - //defer venus.Close() + venus, err := host.ConnectHost(host.URI_QEMU_SSH_SYSTEM, "venus.staur.ca") + if err != nil { + log.Fatal(err) + } + log.Println("Connected to host `venus.staur.ca`") + defer venus.Close() //lm, err := venus.GetGuestByName("logan-minecraft") //if err != nil { // log.Fatal(err) @@ -38,17 +40,16 @@ func main() { r := chi.NewRouter() r.Use(middleware.Logger) + if DEBUG { + r.Use(middleware.NoCache) + } r.Get("/static/*", func(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() - if DEBUG { - w.Header().Add("Cache-Control", "no-cache, no-store, must-revalidate") - w.Header().Add("Pragma", "no-cache") - w.Header().Add("Expire", "0") - } fs.ServeHTTP(w, r) }) r.Get("/", templ.Handler(layouts.Manager("Cluster Manager", "ClustVirt", defaultNavBar)).ServeHTTP) r.Get("/about", templ.Handler(static.Home()).ServeHTTP) + r.Get("/chart", templ.Handler(components.HostInfo(venus)).ServeHTTP) log.Println(http.ListenAndServe(":3000", r)) } diff --git a/public/chart.svg b/public/chart.svg new file mode 100644 index 0000000..8780dd6 --- /dev/null +++ b/public/chart.svg @@ -0,0 +1 @@ +\nFree diff --git a/public/css/style.css b/public/css/style.css index 3a74bfb..c13d444 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -822,45 +822,12 @@ span>a:visited { position: static; } -.order-3 { - order: 3; -} - .order-2 { order: 2; } -.float-left { - float: left; -} - -.m-5 { - margin: 1.25rem; -} - -.mx-5 { - margin-left: 1.25rem; - margin-right: 1.25rem; -} - -.mx-auto { - margin-left: auto; - margin-right: auto; -} - -.my-8 { - margin-top: 2rem; - margin-bottom: 2rem; -} - -.mx-2 { - margin-left: 0.5rem; - margin-right: 0.5rem; -} - -.my-2 { - margin-top: 0.5rem; - margin-bottom: 0.5rem; +.order-3 { + order: 3; } .mx-4 { @@ -868,7 +835,8 @@ span>a:visited { margin-right: 1rem; } -.mr-auto { +.mx-auto { + margin-left: auto; margin-right: auto; } @@ -876,250 +844,38 @@ span>a:visited { display: block; } -.inline-block { - display: inline-block; -} - .flex { display: flex; } -.inline-flex { - display: inline-flex; -} - .contents { display: contents; } -.hidden { - display: none; -} - -.size-fit { - width: -moz-fit-content; - width: fit-content; - height: -moz-fit-content; - height: fit-content; -} - -.size-max { - width: -moz-max-content; - width: max-content; - height: -moz-max-content; - height: max-content; -} - -.size-72 { - width: 18rem; - height: 18rem; -} - -.size-64 { - width: 16rem; - height: 16rem; -} - -.h-4 { - height: 1rem; -} - -.h-8 { - height: 2rem; -} - -.h-full { - height: 100%; +.h-28 { + height: 7rem; } .h-6 { height: 1.5rem; } -.h-12 { - height: 3rem; -} - -.h-14 { - height: 3.5rem; -} - -.h-10 { - height: 2.5rem; -} - -.h-9 { - height: 2.25rem; -} - -.h-11 { - height: 2.75rem; -} - -.h-28 { - height: 7rem; -} - -.h-44 { - height: 11rem; -} - -.h-52 { - height: 13rem; -} - -.h-36 { - height: 9rem; -} - -.h-40 { - height: 10rem; -} - -.h-max { - height: -moz-max-content; - height: max-content; -} - -.h-auto { - height: auto; -} - -.h-48 { - height: 12rem; -} - -.h-56 { - height: 14rem; -} - -.h-64 { - height: 16rem; -} - -.h-72 { - height: 18rem; -} - -.h-fit { - height: -moz-fit-content; - height: fit-content; -} - -.w-full { - width: 100%; -} - -.w-9\/12 { - width: 75%; -} - -.w-80 { - width: 20rem; -} - -.w-fit { - width: -moz-fit-content; - width: fit-content; -} - -.w-10\/12 { - width: 83.333333%; -} - -.w-11\/12 { - width: 91.666667%; -} - -.w-6 { - width: 1.5rem; -} - -.w-40 { - width: 10rem; -} - -.w-44 { - width: 11rem; -} - -.w-auto { - width: auto; -} - -.w-48 { - width: 12rem; -} - -.w-52 { - width: 13rem; -} - -.w-64 { - width: 16rem; -} - -.w-72 { - width: 18rem; -} - -.w-max { - width: -moz-max-content; - width: max-content; +.h-full { + height: 100%; } .flex-auto { flex: 1 1 auto; } -.flex-grow { - flex-grow: 1; -} - -.grow { - flex-grow: 1; -} - .basis-1\/2 { flex-basis: 50%; } -.basis-1\/3 { - flex-basis: 33.333333%; -} - .basis-1\/4 { flex-basis: 25%; } -.basis-2\/3 { - flex-basis: 66.666667%; -} - -.basis-4\/12 { - flex-basis: 33.333333%; -} - -.basis-5\/12 { - flex-basis: 41.666667%; -} - -.basis-7\/12 { - flex-basis: 58.333333%; -} - -.basis-8\/12 { - flex-basis: 66.666667%; -} - -.basis-2\/4 { - flex-basis: 50%; -} - -.basis-2\/5 { - flex-basis: 40%; -} - .list-inside { list-style-position: inside; } @@ -1140,10 +896,6 @@ span>a:visited { list-style-image: url("/static/icons/list-possible.svg"); } -.flex-row { - flex-direction: row; -} - .flex-col { flex-direction: column; } @@ -1160,18 +912,10 @@ span>a:visited { align-items: center; } -.justify-center { - justify-content: center; -} - .justify-between { justify-content: space-between; } -.justify-around { - justify-content: space-around; -} - .gap-2 { gap: 0.5rem; } @@ -1180,11 +924,6 @@ span>a:visited { gap: 1rem; } -.gap-x-2 { - -moz-column-gap: 0.5rem; - column-gap: 0.5rem; -} - .divide-solid > :not([hidden]) ~ :not([hidden]) { border-style: solid; } @@ -9199,22 +8938,6 @@ span>a:visited { border-color: rgb(51 51 30 / 0.95); } -.self-start { - align-self: flex-start; -} - -.self-center { - align-self: center; -} - -.justify-self-end { - justify-self: end; -} - -.justify-self-center { - justify-self: center; -} - .rounded { border-radius: 0.25rem; } @@ -93288,11 +93011,6 @@ span>a:visited { background-color: rgb(51 51 30 / 0.95); } -.bg-red-500 { - --tw-bg-opacity: 1; - background-color: rgb(239 68 68 / var(--tw-bg-opacity)); -} - .from-uiblue-100 { --tw-gradient-from: #dde7f8 var(--tw-gradient-from-position); --tw-gradient-to: rgb(221 231 248 / 0) var(--tw-gradient-to-position); @@ -138837,28 +138555,14 @@ span>a:visited { padding: 0.5rem; } -.px-8 { - padding-left: 2rem; - padding-right: 2rem; -} - -.py-8 { - padding-top: 2rem; - padding-bottom: 2rem; -} - -.align-bottom { - vertical-align: bottom; -} - .text-2xl { font-size: 1.5rem; line-height: 2rem; } -.text-sm { - font-size: 0.875rem; - line-height: 1.25rem; +.text-lg { + font-size: 1.125rem; + line-height: 1.75rem; } .text-xl { @@ -138866,21 +138570,6 @@ span>a:visited { line-height: 1.75rem; } -.text-3xl { - font-size: 1.875rem; - line-height: 2.25rem; -} - -.text-4xl { - font-size: 2.25rem; - line-height: 2.5rem; -} - -.text-lg { - font-size: 1.125rem; - line-height: 1.75rem; -} - .font-bold { font-weight: 700; } @@ -138889,10 +138578,6 @@ span>a:visited { font-weight: 600; } -.font-thin { - font-weight: 100; -} - .italic { font-style: italic; } @@ -220454,28 +220139,20 @@ span>a:visited { } @media (min-width: 640px) { - .sm\:hidden { - display: none; - } - .sm\:w-full { width: 100%; } - .sm\:flex-col { - flex-direction: column; - } - - .sm\:justify-start { - justify-content: flex-start; - } - .sm\:gap-8 { gap: 2rem; } } @media (min-width: 768px) { + .md\:order-1 { + order: 1; + } + .md\:order-2 { order: 2; } @@ -220484,34 +220161,10 @@ span>a:visited { order: 3; } - .md\:order-1 { - order: 1; - } - - .md\:block { - display: block; - } - - .md\:inline-block { - display: inline-block; - } - - .md\:inline-flex { - display: inline-flex; - } - - .md\:contents { - display: contents; - } - .md\:h-16 { height: 4rem; } - .md\:w-10\/12 { - width: 83.333333%; - } - .md\:w-auto { width: auto; } @@ -220538,29 +220191,3 @@ span>a:visited { padding-bottom: 0.5rem; } } - -@media (min-width: 1024px) { - .lg\:h-8 { - height: 2rem; - } - - .lg\:flex-row { - flex-direction: row; - } - - .lg\:items-end { - align-items: flex-end; - } - - .lg\:items-baseline { - align-items: baseline; - } - - .lg\:justify-start { - justify-content: flex-start; - } - - .lg\:justify-center { - justify-content: center; - } -} diff --git a/view/components/hostinfo.templ b/view/components/hostinfo.templ new file mode 100644 index 0000000..230e603 --- /dev/null +++ b/view/components/hostinfo.templ @@ -0,0 +1,12 @@ +package components + +import ( + "git.staur.ca/stobbsm/clustvirt/lib/host" +) + +// HostInfo is meant to be an HTMX response +templ HostInfo(h *host.Host) { + + @templ.Raw(h.ChartMemory()) + +} diff --git a/view/components/hostinfo_templ.go b/view/components/hostinfo_templ.go new file mode 100644 index 0000000..405d91c --- /dev/null +++ b/view/components/hostinfo_templ.go @@ -0,0 +1,48 @@ +// 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 ( + "git.staur.ca/stobbsm/clustvirt/lib/host" +) + +// 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_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("") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ.Raw(h.ChartMemory()).Render(ctx, templ_7745c5c3_Buffer) + 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 + } + if !templ_7745c5c3_IsBuffer { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) + } + return templ_7745c5c3_Err + }) +}