add basic chi http server

- added air to reload the server on changes automatically
- added tailwindcss
- added base page structure in html using _index, _header and _footer
- added static homepage
- added style.css built by tailwindcss
- added basic View object to control views and templates
This commit is contained in:
Matthew Stobbs 2024-03-13 19:12:48 -06:00
parent f96a911722
commit 2419d5d3a6
15 changed files with 930 additions and 15 deletions

46
.air.toml Normal file
View File

@ -0,0 +1,46 @@
root = "."
testdata_dir = "testdata"
tmp_dir = "tmp"
[build]
args_bin = []
bin = "./tmp/main"
cmd = "go build -o ./tmp/main ."
delay = 1000
exclude_dir = ["tmp", "vendor", "testdata", "assets/node_modules"]
exclude_file = []
exclude_regex = ["_test.go"]
exclude_unchanged = false
follow_symlink = false
full_bin = ""
include_dir = []
include_ext = ["go", "tpl", "tmpl", "html", "gohtml", "css"]
include_file = []
kill_delay = "0s"
log = "build-errors.log"
poll = false
poll_interval = 0
post_cmd = []
pre_cmd = []
rerun = false
rerun_delay = 500
send_interrupt = false
stop_on_error = false
[color]
app = ""
build = "yellow"
main = "magenta"
runner = "green"
watcher = "cyan"
[log]
main_only = false
time = false
[misc]
clean_on_exit = false
[screen]
clear_on_rebuild = false
keep_scroll = true

5
assets/package.json Normal file
View File

@ -0,0 +1,5 @@
{
"devDependencies": {
"tailwindcss": "^3.4.1"
}
}

4
assets/src/style.css Normal file
View File

@ -0,0 +1,4 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@ -0,0 +1,9 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ["../view/**/*.gohtml"],
theme: {
extend: {},
},
plugins: [],
}

5
go.mod
View File

@ -4,4 +4,7 @@ go 1.22.1
require libvirt.org/go/libvirt v1.10001.0
require gopkg.in/ffmt.v1 v1.5.6 // indirect
require (
github.com/go-chi/chi/v5 v5.0.12 // indirect
gopkg.in/ffmt.v1 v1.5.6 // indirect
)

2
go.sum
View File

@ -1,3 +1,5 @@
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=
gopkg.in/ffmt.v1 v1.5.6 h1:4Bu3riZp5sAIXW2T/18JM9BkwJLodurXFR0f7PXp+cw=
gopkg.in/ffmt.v1 v1.5.6/go.mod h1:LssvGOZFiBGoBcobkTqnyh+uN1VzIRoibW+c0JI/Ha4=
libvirt.org/go/libvirt v1.10001.0 h1:lEVDNE7xfzmZXiDEGIS8NvJSuaz11OjRXw+ufbQEtPY=

63
main.go
View File

@ -2,24 +2,63 @@ package main
import (
"log"
"net/http"
"git.staur.ca/stobbsm/clustvirt/lib/host"
ffmt "gopkg.in/ffmt.v1"
"git.staur.ca/stobbsm/clustvirt/view"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
)
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)
//venus, err := host.ConnectHost(host.URI_QEMU_SSH_SYSTEM, "venus.staur.ca")
//if err != nil {
// log.Fatal(err)
//}
//defer venus.Close()
//lm, err := venus.GetGuestByName("logan-minecraft")
//if err != nil {
// log.Fatal(err)
//}
//defer lm.Close()
// Start webserver and serve homepage
fs := http.StripPrefix("/static/", http.FileServer(http.Dir("public")))
r := chi.NewRouter()
r.Use(middleware.Logger)
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
if DEBUG {
w.Header().Add("Cache-Control", "no-cache, no-store, must-revalidate")
w.Header().Add("Pragma", "no-cache")
w.Header().Add("Expire", "0")
}
defer venus.Close()
ffmt.P(venus)
lm, err := venus.GetGuestByName("logan-minecraft")
if err != nil {
log.Fatal(err)
log.Println(w.Write([]byte("Nothing on / yet")))
})
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")
}
defer lm.Close()
//ffmt.P(lm)
fs.ServeHTTP(w, r)
})
r.Get("/home", func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
if DEBUG {
w.Header().Add("Cache-Control", "no-cache, no-store, must-revalidate")
w.Header().Add("Pragma", "no-cache")
w.Header().Add("Expire", "0")
}
w.Header().Add("Content", "text/html")
log.Println(view.ViewHome.Render(w, nil))
})
log.Println(http.ListenAndServe(":3000", r))
}

580
public/css/style.css Normal file
View File

@ -0,0 +1,580 @@
/*
! tailwindcss v3.4.1 | MIT License | https://tailwindcss.com
*/
/*
1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
*/
*,
::before,
::after {
box-sizing: border-box;
/* 1 */
border-width: 0;
/* 2 */
border-style: solid;
/* 2 */
border-color: #e5e7eb;
/* 2 */
}
::before,
::after {
--tw-content: '';
}
/*
1. Use a consistent sensible line-height in all browsers.
2. Prevent adjustments of font size after orientation changes in iOS.
3. Use a more readable tab size.
4. Use the user's configured `sans` font-family by default.
5. Use the user's configured `sans` font-feature-settings by default.
6. Use the user's configured `sans` font-variation-settings by default.
7. Disable tap highlights on iOS
*/
html,
:host {
line-height: 1.5;
/* 1 */
-webkit-text-size-adjust: 100%;
/* 2 */
-moz-tab-size: 4;
/* 3 */
-o-tab-size: 4;
tab-size: 4;
/* 3 */
font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
/* 4 */
font-feature-settings: normal;
/* 5 */
font-variation-settings: normal;
/* 6 */
-webkit-tap-highlight-color: transparent;
/* 7 */
}
/*
1. Remove the margin in all browsers.
2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.
*/
body {
margin: 0;
/* 1 */
line-height: inherit;
/* 2 */
}
/*
1. Add the correct height in Firefox.
2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
3. Ensure horizontal rules are visible by default.
*/
hr {
height: 0;
/* 1 */
color: inherit;
/* 2 */
border-top-width: 1px;
/* 3 */
}
/*
Add the correct text decoration in Chrome, Edge, and Safari.
*/
abbr:where([title]) {
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
}
/*
Remove the default font size and weight for headings.
*/
h1,
h2,
h3,
h4,
h5,
h6 {
font-size: inherit;
font-weight: inherit;
}
/*
Reset links to optimize for opt-in styling instead of opt-out.
*/
a {
color: inherit;
text-decoration: inherit;
}
/*
Add the correct font weight in Edge and Safari.
*/
b,
strong {
font-weight: bolder;
}
/*
1. Use the user's configured `mono` font-family by default.
2. Use the user's configured `mono` font-feature-settings by default.
3. Use the user's configured `mono` font-variation-settings by default.
4. Correct the odd `em` font sizing in all browsers.
*/
code,
kbd,
samp,
pre {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
/* 1 */
font-feature-settings: normal;
/* 2 */
font-variation-settings: normal;
/* 3 */
font-size: 1em;
/* 4 */
}
/*
Add the correct font size in all browsers.
*/
small {
font-size: 80%;
}
/*
Prevent `sub` and `sup` elements from affecting the line height in all browsers.
*/
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
/*
1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
3. Remove gaps between table borders by default.
*/
table {
text-indent: 0;
/* 1 */
border-color: inherit;
/* 2 */
border-collapse: collapse;
/* 3 */
}
/*
1. Change the font styles in all browsers.
2. Remove the margin in Firefox and Safari.
3. Remove default padding in all browsers.
*/
button,
input,
optgroup,
select,
textarea {
font-family: inherit;
/* 1 */
font-feature-settings: inherit;
/* 1 */
font-variation-settings: inherit;
/* 1 */
font-size: 100%;
/* 1 */
font-weight: inherit;
/* 1 */
line-height: inherit;
/* 1 */
color: inherit;
/* 1 */
margin: 0;
/* 2 */
padding: 0;
/* 3 */
}
/*
Remove the inheritance of text transform in Edge and Firefox.
*/
button,
select {
text-transform: none;
}
/*
1. Correct the inability to style clickable types in iOS and Safari.
2. Remove default button styles.
*/
button,
[type='button'],
[type='reset'],
[type='submit'] {
-webkit-appearance: button;
/* 1 */
background-color: transparent;
/* 2 */
background-image: none;
/* 2 */
}
/*
Use the modern Firefox focus style for all focusable elements.
*/
:-moz-focusring {
outline: auto;
}
/*
Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
*/
:-moz-ui-invalid {
box-shadow: none;
}
/*
Add the correct vertical alignment in Chrome and Firefox.
*/
progress {
vertical-align: baseline;
}
/*
Correct the cursor style of increment and decrement buttons in Safari.
*/
::-webkit-inner-spin-button,
::-webkit-outer-spin-button {
height: auto;
}
/*
1. Correct the odd appearance in Chrome and Safari.
2. Correct the outline style in Safari.
*/
[type='search'] {
-webkit-appearance: textfield;
/* 1 */
outline-offset: -2px;
/* 2 */
}
/*
Remove the inner padding in Chrome and Safari on macOS.
*/
::-webkit-search-decoration {
-webkit-appearance: none;
}
/*
1. Correct the inability to style clickable types in iOS and Safari.
2. Change font properties to `inherit` in Safari.
*/
::-webkit-file-upload-button {
-webkit-appearance: button;
/* 1 */
font: inherit;
/* 2 */
}
/*
Add the correct display in Chrome and Safari.
*/
summary {
display: list-item;
}
/*
Removes the default spacing and border for appropriate elements.
*/
blockquote,
dl,
dd,
h1,
h2,
h3,
h4,
h5,
h6,
hr,
figure,
p,
pre {
margin: 0;
}
fieldset {
margin: 0;
padding: 0;
}
legend {
padding: 0;
}
ol,
ul,
menu {
list-style: none;
margin: 0;
padding: 0;
}
/*
Reset default styling for dialogs.
*/
dialog {
padding: 0;
}
/*
Prevent resizing textareas horizontally by default.
*/
textarea {
resize: vertical;
}
/*
1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
2. Set the default placeholder color to the user's configured gray 400 color.
*/
input::-moz-placeholder, textarea::-moz-placeholder {
opacity: 1;
/* 1 */
color: #9ca3af;
/* 2 */
}
input::placeholder,
textarea::placeholder {
opacity: 1;
/* 1 */
color: #9ca3af;
/* 2 */
}
/*
Set the default cursor for buttons.
*/
button,
[role="button"] {
cursor: pointer;
}
/*
Make sure disabled buttons don't get the pointer cursor.
*/
:disabled {
cursor: default;
}
/*
1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
This can trigger a poorly considered lint error in some tools but is included by design.
*/
img,
svg,
video,
canvas,
audio,
iframe,
embed,
object {
display: block;
/* 1 */
vertical-align: middle;
/* 2 */
}
/*
Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
*/
img,
video {
max-width: 100%;
height: auto;
}
/* Make elements with the HTML hidden attribute stay hidden by default */
[hidden] {
display: none;
}
*, ::before, ::after {
--tw-border-spacing-x: 0;
--tw-border-spacing-y: 0;
--tw-translate-x: 0;
--tw-translate-y: 0;
--tw-rotate: 0;
--tw-skew-x: 0;
--tw-skew-y: 0;
--tw-scale-x: 1;
--tw-scale-y: 1;
--tw-pan-x: ;
--tw-pan-y: ;
--tw-pinch-zoom: ;
--tw-scroll-snap-strictness: proximity;
--tw-gradient-from-position: ;
--tw-gradient-via-position: ;
--tw-gradient-to-position: ;
--tw-ordinal: ;
--tw-slashed-zero: ;
--tw-numeric-figure: ;
--tw-numeric-spacing: ;
--tw-numeric-fraction: ;
--tw-ring-inset: ;
--tw-ring-offset-width: 0px;
--tw-ring-offset-color: #fff;
--tw-ring-color: rgb(59 130 246 / 0.5);
--tw-ring-offset-shadow: 0 0 #0000;
--tw-ring-shadow: 0 0 #0000;
--tw-shadow: 0 0 #0000;
--tw-shadow-colored: 0 0 #0000;
--tw-blur: ;
--tw-brightness: ;
--tw-contrast: ;
--tw-grayscale: ;
--tw-hue-rotate: ;
--tw-invert: ;
--tw-saturate: ;
--tw-sepia: ;
--tw-drop-shadow: ;
--tw-backdrop-blur: ;
--tw-backdrop-brightness: ;
--tw-backdrop-contrast: ;
--tw-backdrop-grayscale: ;
--tw-backdrop-hue-rotate: ;
--tw-backdrop-invert: ;
--tw-backdrop-opacity: ;
--tw-backdrop-saturate: ;
--tw-backdrop-sepia: ;
}
::backdrop {
--tw-border-spacing-x: 0;
--tw-border-spacing-y: 0;
--tw-translate-x: 0;
--tw-translate-y: 0;
--tw-rotate: 0;
--tw-skew-x: 0;
--tw-skew-y: 0;
--tw-scale-x: 1;
--tw-scale-y: 1;
--tw-pan-x: ;
--tw-pan-y: ;
--tw-pinch-zoom: ;
--tw-scroll-snap-strictness: proximity;
--tw-gradient-from-position: ;
--tw-gradient-via-position: ;
--tw-gradient-to-position: ;
--tw-ordinal: ;
--tw-slashed-zero: ;
--tw-numeric-figure: ;
--tw-numeric-spacing: ;
--tw-numeric-fraction: ;
--tw-ring-inset: ;
--tw-ring-offset-width: 0px;
--tw-ring-offset-color: #fff;
--tw-ring-color: rgb(59 130 246 / 0.5);
--tw-ring-offset-shadow: 0 0 #0000;
--tw-ring-shadow: 0 0 #0000;
--tw-shadow: 0 0 #0000;
--tw-shadow-colored: 0 0 #0000;
--tw-blur: ;
--tw-brightness: ;
--tw-contrast: ;
--tw-grayscale: ;
--tw-hue-rotate: ;
--tw-invert: ;
--tw-saturate: ;
--tw-sepia: ;
--tw-drop-shadow: ;
--tw-backdrop-blur: ;
--tw-backdrop-brightness: ;
--tw-backdrop-contrast: ;
--tw-backdrop-grayscale: ;
--tw-backdrop-hue-rotate: ;
--tw-backdrop-invert: ;
--tw-backdrop-opacity: ;
--tw-backdrop-saturate: ;
--tw-backdrop-sepia: ;
}
.container {
width: 100%;
}
@media (min-width: 640px) {
.container {
max-width: 640px;
}
}
@media (min-width: 768px) {
.container {
max-width: 768px;
}
}
@media (min-width: 1024px) {
.container {
max-width: 1024px;
}
}
@media (min-width: 1280px) {
.container {
max-width: 1280px;
}
}
@media (min-width: 1536px) {
.container {
max-width: 1536px;
}
}

6
view/_footer.gohtml Normal file
View File

@ -0,0 +1,6 @@
{{ define "footer" }}
<hr>
<div id="footer_left">Left</div>
<div id="footer_middle">Middle</div>
<div id="footer_right">Right</div>
{{ end }}

10
view/_header.gohtml Normal file
View File

@ -0,0 +1,10 @@
{{ define "header" }}
<h1>Clustvirt</h1>
<h2>Libvirtd simplified and clustered</h2>
<nav>
<ul>
<li>Server 1</li>
<li>Server 2</li>
</ul>
</nav>
{{ end }}

22
view/_index.gohtml Normal file
View File

@ -0,0 +1,22 @@
{{ define "_index" }}
<!DOCTYPE html>
<html>
<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 }}

4
view/host.gohtml Normal file
View File

@ -0,0 +1,4 @@
{{ define "content" }}
<h1>{{.HostName}}</h1>
<p>This will contain all the graphs, storage pool lists, vm lists, etc for the host selected.</p>
{{ end }}

43
view/pages.go Normal file
View File

@ -0,0 +1,43 @@
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)
}

87
view/static/home.gohtml Normal file
View File

@ -0,0 +1,87 @@
{{ define "content" }}
<h3>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>
<p>
The aims of this project are:
<ul>
<li>Base OS Agnostic. If it can run libvirtd, this should be able to control it on some level</li>
<li>Open source, always</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>
</p>
<p>
What this project does not, but may someday do (future goals):
<ul>
<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>
</p>
<p>
What this project will NEVER do, even if asked really nicely:
<ul>
<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>
</p>
<p>
Why does this even exist?
<ul>
<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>
</p>
<p>I recently created a <a href="http://redd.it/1bct15z">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
Infrastructer Operating System" offerings out there. This will not take over your base operating system 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>
{{ end }}

55
view/view.go Normal file
View File

@ -0,0 +1,55 @@
// 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)
}