Compare commits

...

4 Commits

Author SHA1 Message Date
6e0198de35 fixed list icons
- instead of trying to change their colors in css, i created new svgs based on the ones I wanted
2024-03-14 01:54:41 -06:00
c170739128 remove resized svgs. It was only the viewbox 2024-03-13 22:38:35 -06:00
fada05c56d add icons for tailwind
- svg icons from heroicons.com
- add Type to String map for:
  - StoragePoolState
  - StorageVolType
- default icons are sized 24x24
  - added 36x36, 48x48, 64x64
- adding styling to home.gohtml homepage
2024-03-13 22:21:01 -06:00
2419d5d3a6 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
2024-03-13 19:12:48 -06:00
66 changed files with 3368 additions and 20 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

1408
assets/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

7
assets/package.json Normal file
View File

@ -0,0 +1,7 @@
{
"devDependencies": {
"@tailwindcss/forms": "^0.5.7",
"tailwind-dracula": "^1.1.0",
"tailwindcss": "^3.4.1"
}
}

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

@ -0,0 +1,10 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@tailwind variants;
@layer components {
.notimpl {
@apply italic font-light list-image-possible;
}
}

22
assets/tailwind.config.js Normal file
View File

@ -0,0 +1,22 @@
/** @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')(),
],
}

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=

View File

@ -12,6 +12,8 @@ import (
"git.staur.ca/stobbsm/clustvirt/lib/guest"
"git.staur.ca/stobbsm/clustvirt/lib/secret"
"git.staur.ca/stobbsm/clustvirt/lib/storagepool"
"git.staur.ca/stobbsm/clustvirt/lib/storagevol"
"git.staur.ca/stobbsm/clustvirt/util"
"libvirt.org/go/libvirt"
)
@ -98,6 +100,11 @@ type StoragePoolInfo struct {
XML string
Active bool
Persistent bool
AutoStart bool
State string
Capacity uint64
Allocation uint64
Available uint64
// HAEnabled indicates if the storage pool has High Availability
HAEnabled bool
// Volumes defined in the storage pool
@ -107,11 +114,12 @@ type StoragePoolInfo struct {
// VolumeInfo holds basic information about Volumes available in storage pools
type VolumeInfo struct {
Name string
// StoragePool this volume is part of
StoragePool StoragePoolInfo
Type string
Size uint
XML string
Key string
Path string
Type string
Capacity uint64
Allocation uint64
XML string
}
// NetIfInfo holds basic information about available network interfaces (not their connections, the devices themselves)
@ -265,6 +273,48 @@ func (h *Host) getStoragePools(wg *sync.WaitGroup) {
if h.StoragePoolList[i].Persistent, err = s.IsPersistent(); err != nil {
log.Println(err)
}
if h.StoragePoolList[i].AutoStart, err = s.GetAutostart(); err != nil {
log.Println(err)
}
spInfo, err := s.GetInfo()
if err != nil {
log.Println(err)
}
h.StoragePoolList[i].State = storagepool.StoragePoolStateMap[spInfo.State]
h.StoragePoolList[i].Capacity = spInfo.Capacity
h.StoragePoolList[i].Allocation = spInfo.Allocation
h.StoragePoolList[i].Available = spInfo.Available
svols, err := s.ListAllStorageVolumes(0)
if err != nil {
log.Println(err)
}
if len(svols) > 0 {
h.StoragePoolList[i].Volumes = make([]VolumeInfo, len(svols))
for j, sv := range svols {
if h.StoragePoolList[i].Volumes[j].Name, err = sv.GetName(); err != nil {
log.Println(err)
}
if h.StoragePoolList[i].Volumes[j].Key, err = sv.GetKey(); err != nil {
log.Println(err)
}
if h.StoragePoolList[i].Volumes[j].Path, err = sv.GetPath(); err != nil {
log.Println(err)
}
svInfo, err := sv.GetInfo() // Type, Capacity, Allocation
if err != nil {
log.Println(err)
}
h.StoragePoolList[i].Volumes[j].Type = storagevol.StorageVolTypeMap[svInfo.Type]
h.StoragePoolList[i].Volumes[j].Capacity = svInfo.Capacity
h.StoragePoolList[i].Volumes[j].Allocation = svInfo.Allocation
if h.StoragePoolList[i].Volumes[j].XML, err = sv.GetXMLDesc(0); err != nil {
log.Println(err)
}
sv.Free()
}
}
s.Free()
}

6
lib/host/view.go Normal file
View File

@ -0,0 +1,6 @@
package host
import "github.com/go-chi/chi/v5"
func (h *Host) HTMXRenderHandler(router *chi.Router) {
}

View File

@ -1 +1,11 @@
package storagepool
import "libvirt.org/go/libvirt"
var StoragePoolStateMap = map[libvirt.StoragePoolState]string{
libvirt.STORAGE_POOL_INACTIVE: "inactive",
libvirt.STORAGE_POOL_BUILDING: "building",
libvirt.STORAGE_POOL_RUNNING: "running",
libvirt.STORAGE_POOL_DEGRADED: "degraded",
libvirt.STORAGE_POOL_INACCESSIBLE: "inaccessible",
}

12
lib/storagevol/lib.go Normal file
View File

@ -0,0 +1,12 @@
package storagevol
import "libvirt.org/go/libvirt"
var StorageVolTypeMap = map[libvirt.StorageVolType]string{
libvirt.STORAGE_VOL_FILE: "file",
libvirt.STORAGE_VOL_BLOCK: "block",
libvirt.STORAGE_VOL_DIR: "dir",
libvirt.STORAGE_VOL_NETWORK: "network",
libvirt.STORAGE_VOL_NETDIR: "netdir",
libvirt.STORAGE_VOL_PLOOP: "ploop",
}

58
main.go
View File

@ -2,24 +2,54 @@ 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)
}
defer venus.Close()
ffmt.P(venus)
lm, err := venus.GetGuestByName("logan-minecraft")
if err != nil {
log.Fatal(err)
}
defer lm.Close()
//ffmt.P(lm)
//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("/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("/", 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))
}

1090
public/css/style.css Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M10.5 6h9.75M10.5 6a1.5 1.5 0 1 1-3 0m3 0a1.5 1.5 0 1 0-3 0M3.75 6H7.5m3 12h9.75m-9.75 0a1.5 1.5 0 0 1-3 0m3 0a1.5 1.5 0 0 0-3 0m-3.75 0H7.5m9-6h3.75m-3.75 0a1.5 1.5 0 0 1-3 0m3 0a1.5 1.5 0 0 0-3 0m-9.75 0h9.75" />
</svg>

After

Width:  |  Height:  |  Size: 411 B

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9.75 14.25 12m0 0 2.25 2.25M14.25 12l2.25-2.25M14.25 12 12 14.25m-2.58 4.92-6.374-6.375a1.125 1.125 0 0 1 0-1.59L9.42 4.83c.21-.211.497-.33.795-.33H19.5a2.25 2.25 0 0 1 2.25 2.25v10.5a2.25 2.25 0 0 1-2.25 2.25h-9.284c-.298 0-.585-.119-.795-.33Z" />
</svg>

After

Width:  |  Height:  |  Size: 449 B

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M14.857 17.082a23.848 23.848 0 0 0 5.454-1.31A8.967 8.967 0 0 1 18 9.75V9A6 6 0 0 0 6 9v.75a8.967 8.967 0 0 1-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 0 1-5.714 0m5.714 0a3 3 0 1 1-5.714 0M3.124 7.5A8.969 8.969 0 0 1 5.292 3m13.416 0a8.969 8.969 0 0 1 2.168 4.5" />
</svg>

After

Width:  |  Height:  |  Size: 488 B

4
public/icons/bell.svg Normal file
View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M14.857 17.082a23.848 23.848 0 0 0 5.454-1.31A8.967 8.967 0 0 1 18 9.75V9A6 6 0 0 0 6 9v.75a8.967 8.967 0 0 1-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 0 1-5.714 0m5.714 0a3 3 0 1 1-5.714 0" />
</svg>

After

Width:  |  Height:  |  Size: 415 B

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M11.412 15.655 9.75 21.75l3.745-4.012M9.257 13.5H3.75l2.659-2.849m2.048-2.194L14.25 2.25 12 10.5h8.25l-4.707 5.043M8.457 8.457 3 3m5.457 5.457 7.086 7.086m0 0L21 21" />
</svg>

After

Width:  |  Height:  |  Size: 365 B

4
public/icons/bolt.svg Normal file
View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="m3.75 13.5 10.5-11.25L12 10.5h8.25L9.75 21.75 12 13.5H3.75Z" />
</svg>

After

Width:  |  Height:  |  Size: 260 B

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 0 1 2.25-2.25h13.5A2.25 2.25 0 0 1 21 7.5v11.25m-18 0A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75m-18 0v-7.5A2.25 2.25 0 0 1 5.25 9h13.5A2.25 2.25 0 0 1 21 11.25v7.5m-9-6h.008v.008H12v-.008ZM12 15h.008v.008H12V15Zm0 2.25h.008v.008H12v-.008ZM9.75 15h.008v.008H9.75V15Zm0 2.25h.008v.008H9.75v-.008ZM7.5 15h.008v.008H7.5V15Zm0 2.25h.008v.008H7.5v-.008Zm6.75-4.5h.008v.008h-.008v-.008Zm0 2.25h.008v.008h-.008V15Zm0 2.25h.008v.008h-.008v-.008Zm2.25-4.5h.008v.008H16.5v-.008Zm0 2.25h.008v.008H16.5V15Z" />
</svg>

After

Width:  |  Height:  |  Size: 756 B

5
public/icons/camera.svg Normal file
View File

@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M6.827 6.175A2.31 2.31 0 0 1 5.186 7.23c-.38.054-.757.112-1.134.175C2.999 7.58 2.25 8.507 2.25 9.574V18a2.25 2.25 0 0 0 2.25 2.25h15A2.25 2.25 0 0 0 21.75 18V9.574c0-1.067-.75-1.994-1.802-2.169a47.865 47.865 0 0 0-1.134-.175 2.31 2.31 0 0 1-1.64-1.055l-.822-1.316a2.192 2.192 0 0 0-1.736-1.039 48.774 48.774 0 0 0-5.232 0 2.192 2.192 0 0 0-1.736 1.039l-.821 1.316Z" />
<path stroke-linecap="round" stroke-linejoin="round" d="M16.5 12.75a4.5 4.5 0 1 1-9 0 4.5 4.5 0 0 1 9 0ZM18.75 10.5h.008v.008h-.008V10.5Z" />
</svg>

After

Width:  |  Height:  |  Size: 709 B

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75 11.25 15 15 9.75M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
</svg>

After

Width:  |  Height:  |  Size: 263 B

4
public/icons/check.svg Normal file
View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="m4.5 12.75 6 6 9-13.5" />
</svg>

After

Width:  |  Height:  |  Size: 222 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="m8.25 4.5 7.5 7.5-7.5 7.5" />
</svg>

After

Width:  |  Height:  |  Size: 225 B

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M20.25 6.375c0 2.278-3.694 4.125-8.25 4.125S3.75 8.653 3.75 6.375m16.5 0c0-2.278-3.694-4.125-8.25-4.125S3.75 4.097 3.75 6.375m16.5 0v11.25c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125V6.375m16.5 0v3.75m-16.5-3.75v3.75m16.5 0v3.75C20.25 16.153 16.556 18 12 18s-8.25-1.847-8.25-4.125v-3.75m16.5 0c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125" />
</svg>

After

Width:  |  Height:  |  Size: 558 B

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M17.25 6.75 22.5 12l-5.25 5.25m-10.5 0L1.5 12l5.25-5.25m7.5-3-4.5 16.5" />
</svg>

After

Width:  |  Height:  |  Size: 271 B

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="m6.75 7.5 3 2.25-3 2.25m4.5 0h3m-9 8.25h13.5A2.25 2.25 0 0 0 21 18V6a2.25 2.25 0 0 0-2.25-2.25H5.25A2.25 2.25 0 0 0 3 6v12a2.25 2.25 0 0 0 2.25 2.25Z" />
</svg>

After

Width:  |  Height:  |  Size: 350 B

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 17.25v1.007a3 3 0 0 1-.879 2.122L7.5 21h9l-.621-.621A3 3 0 0 1 15 18.257V17.25m6-12V15a2.25 2.25 0 0 1-2.25 2.25H5.25A2.25 2.25 0 0 1 3 15V5.25m18 0A2.25 2.25 0 0 0 18.75 3H5.25A2.25 2.25 0 0 0 3 5.25m18 0V12a2.25 2.25 0 0 1-2.25 2.25H5.25A2.25 2.25 0 0 1 3 12V5.25" />
</svg>

After

Width:  |  Height:  |  Size: 469 B

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-6 h-6">
<path d="M12.378 1.602a.75.75 0 0 0-.756 0L3 6.632l9 5.25 9-5.25-8.622-5.03ZM21.75 7.93l-9 5.25v9l8.628-5.032a.75.75 0 0 0 .372-.648V7.93ZM11.25 22.18v-9l-9-5.25v8.57a.75.75 0 0 0 .372.648l8.628 5.033Z" />
</svg>

After

Width:  |  Height:  |  Size: 313 B

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="m21 7.5-2.25-1.313M21 7.5v2.25m0-2.25-2.25 1.313M3 7.5l2.25-1.313M3 7.5l2.25 1.313M3 7.5v2.25m9 3 2.25-1.313M12 12.75l-2.25-1.313M12 12.75V15m0 6.75 2.25-1.313M12 21.75V19.5m0 2.25-2.25-1.313m0-16.875L12 2.25l2.25 1.313M21 14.25v2.25l-2.25 1.313m-13.5 0L3 16.5v-2.25" />
</svg>

After

Width:  |  Height:  |  Size: 467 B

4
public/icons/cube.svg Normal file
View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="m21 7.5-9-5.25L3 7.5m18 0-9 5.25m9-5.25v9l-9 5.25M3 7.5l9 5.25M3 7.5v9l9 5.25m0-9v9" />
</svg>

After

Width:  |  Height:  |  Size: 284 B

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m9-.75a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 3.75h.008v.008H12v-.008Z" />
</svg>

After

Width:  |  Height:  |  Size: 276 B

4
public/icons/hashtag.svg Normal file
View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M5.25 8.25h15m-16.5 7.5h15m-1.8-13.5-3.9 19.5m-2.1-19.5-3.9 19.5" />
</svg>

After

Width:  |  Height:  |  Size: 265 B

3
public/icons/home.svg Normal file
View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="m2.25 12 8.954-8.955c.44-.439 1.152-.439 1.591 0L21.75 12M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25" />
</svg>

After

Width:  |  Height:  |  Size: 434 B

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 3.75H6.912a2.25 2.25 0 0 0-2.15 1.588L2.35 13.177a2.25 2.25 0 0 0-.1.661V18a2.25 2.25 0 0 0 2.25 2.25h15A2.25 2.25 0 0 0 21.75 18v-4.162c0-.224-.034-.447-.1-.661L19.24 5.338a2.25 2.25 0 0 0-2.15-1.588H15M2.25 13.5h3.86a2.25 2.25 0 0 1 2.012 1.244l.256.512a2.25 2.25 0 0 0 2.013 1.244h3.218a2.25 2.25 0 0 0 2.013-1.244l.256-.512a2.25 2.25 0 0 1 2.013-1.244h3.859M12 3v8.25m0 0-3-3m3 3 3-3" />
</svg>

After

Width:  |  Height:  |  Size: 591 B

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="m7.875 14.25 1.214 1.942a2.25 2.25 0 0 0 1.908 1.058h2.006c.776 0 1.497-.4 1.908-1.058l1.214-1.942M2.41 9h4.636a2.25 2.25 0 0 1 1.872 1.002l.164.246a2.25 2.25 0 0 0 1.872 1.002h2.092a2.25 2.25 0 0 0 1.872-1.002l.164-.246A2.25 2.25 0 0 1 16.954 9h4.636M2.41 9a2.25 2.25 0 0 0-.16.832V12a2.25 2.25 0 0 0 2.25 2.25h15A2.25 2.25 0 0 0 21.75 12V9.832c0-.287-.055-.57-.16-.832M2.41 9a2.25 2.25 0 0 1 .382-.632l3.285-3.832a2.25 2.25 0 0 1 1.708-.786h8.43c.657 0 1.281.287 1.709.786l3.284 3.832c.163.19.291.404.382.632M4.5 20.25h15A2.25 2.25 0 0 0 21.75 18v-2.625c0-.621-.504-1.125-1.125-1.125H3.375c-.621 0-1.125.504-1.125 1.125V18a2.25 2.25 0 0 0 2.25 2.25Z" />
</svg>

After

Width:  |  Height:  |  Size: 852 B

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="m11.25 11.25.041-.02a.75.75 0 0 1 1.063.852l-.708 2.836a.75.75 0 0 0 1.063.853l.041-.021M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9-3.75h.008v.008H12V8.25Z" />
</svg>

After

Width:  |  Height:  |  Size: 353 B

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 5.25a3 3 0 0 1 3 3m3 0a6 6 0 0 1-7.029 5.912c-.563-.097-1.159.026-1.563.43L10.5 17.25H8.25v2.25H6v2.25H2.25v-2.818c0-.597.237-1.17.659-1.591l6.499-6.499c.404-.404.527-1 .43-1.563A6 6 0 1 1 21.75 8.25Z" />
</svg>

After

Width:  |  Height:  |  Size: 408 B

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 18v-5.25m0 0a6.01 6.01 0 0 0 1.5-.189m-1.5.189a6.01 6.01 0 0 1-1.5-.189m3.75 7.478a12.06 12.06 0 0 1-4.5 0m3.75 2.383a14.406 14.406 0 0 1-3 0M14.25 18v-.192c0-.983.658-1.823 1.508-2.316a7.5 7.5 0 1 0-7.517 0c.85.493 1.509 1.333 1.509 2.316V18" />
</svg>

After

Width:  |  Height:  |  Size: 447 B

4
public/icons/link.svg Normal file
View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M13.19 8.688a4.5 4.5 0 0 1 1.242 7.244l-4.5 4.5a4.5 4.5 0 0 1-6.364-6.364l1.757-1.757m13.35-.622 1.757-1.757a4.5 4.5 0 0 0-6.364-6.364l-4.5 4.5a4.5 4.5 0 0 0 1.242 7.244" />
</svg>

After

Width:  |  Height:  |  Size: 370 B

View File

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
fill="none"
viewBox="0 0 23.999992 23.999996"
stroke-width="1.5"
stroke="currentColor"
class="w-6 h-6"
version="1.1"
id="svg1"
sodipodi:docname="list-accepted.svg"
inkscape:version="1.3.2 (091e20e, 2023-11-25)"
width="23.999992"
height="23.999996"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="9.8333333"
inkscape:cx="12"
inkscape:cy="12"
inkscape:window-width="1440"
inkscape:window-height="847"
inkscape:window-x="0"
inkscape:window-y="25"
inkscape:window-maximized="1"
inkscape:current-layer="svg1" />
<path
stroke-linecap="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"
id="path1"
style="stroke-width:1.84615;stroke:#d6d6d6;stroke-opacity:1" />
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M8.25 6.75h12M8.25 12h12m-12 5.25h12M3.75 6.75h.007v.008H3.75V6.75Zm.375 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0ZM3.75 12h.007v.008H3.75V12Zm.375 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm-.375 5.25h.007v.008H3.75v-.008Zm.375 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Z" />
</svg>

After

Width:  |  Height:  |  Size: 478 B

View File

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
fill="none"
viewBox="0 0 23.999998 23.999996"
stroke-width="1.5"
stroke="currentColor"
class="w-6 h-6"
version="1.1"
id="svg1"
sodipodi:docname="list-informational.svg"
width="23.999998"
height="23.999996"
inkscape:version="1.3.2 (091e20e, 2023-11-25)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="9.8333333"
inkscape:cx="9.7627119"
inkscape:cy="9.7627119"
inkscape:window-width="1312"
inkscape:window-height="449"
inkscape:window-x="0"
inkscape:window-y="25"
inkscape:window-maximized="0"
inkscape:current-layer="svg1" />
<path
stroke-linecap="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"
id="path1"
style="stroke:#d6d6d6;stroke-width:1.84615;stroke-opacity:1" />
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
fill="none"
viewBox="0 0 23.994221 23.935944"
stroke-width="1.5"
stroke="currentColor"
class="w-6 h-6"
version="1.1"
id="svg1"
sodipodi:docname="list-never.svg"
width="23.994221"
height="23.935944"
inkscape:version="1.3.2 (091e20e, 2023-11-25)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="9.8333333"
inkscape:cx="12"
inkscape:cy="12"
inkscape:window-width="1440"
inkscape:window-height="847"
inkscape:window-x="0"
inkscape:window-y="25"
inkscape:window-maximized="1"
inkscape:current-layer="svg1" />
<path
stroke-linecap="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"
id="path1"
style="stroke:#d6d6d6;stroke-width:1.84462;stroke-opacity:1" />
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
fill="none"
viewBox="0 0 23.999998 23.999996"
stroke-width="1.5"
stroke="currentColor"
class="w-6 h-6"
version="1.1"
id="svg1"
sodipodi:docname="list-possible.svg"
width="23.999998"
height="23.999996"
inkscape:version="1.3.2 (091e20e, 2023-11-25)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="9.8333333"
inkscape:cx="9.7627119"
inkscape:cy="9.7627119"
inkscape:window-width="1440"
inkscape:window-height="847"
inkscape:window-x="0"
inkscape:window-y="25"
inkscape:window-maximized="1"
inkscape:current-layer="svg1" />
<path
stroke-linecap="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"
id="path1"
style="stroke:#d6d6d6;stroke-width:1.84615;stroke-opacity:1" />
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z" />
</svg>

After

Width:  |  Height:  |  Size: 278 B

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12H9m12 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
</svg>

After

Width:  |  Height:  |  Size: 245 B

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M18.364 18.364A9 9 0 0 0 5.636 5.636m12.728 12.728A9 9 0 0 1 5.636 5.636m12.728 12.728L5.636 5.636" />
</svg>

After

Width:  |  Height:  |  Size: 299 B

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M14.25 9v6m-4.5 0V9M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
</svg>

After

Width:  |  Height:  |  Size: 257 B

View File

@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
<path stroke-linecap="round" stroke-linejoin="round" d="M15.91 11.672a.375.375 0 0 1 0 .656l-5.603 3.113a.375.375 0 0 1-.557-.328V8.887c0-.286.307-.466.557-.327l5.603 3.112Z" />
</svg>

After

Width:  |  Height:  |  Size: 418 B

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v6m3-3H9m12 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
</svg>

After

Width:  |  Height:  |  Size: 250 B

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M9.879 7.519c1.171-1.025 3.071-1.025 4.242 0 1.172 1.025 1.172 2.687 0 3.712-.203.179-.43.326-.67.442-.745.361-1.45.999-1.45 1.827v.75M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 5.25h.008v.008H12v-.008Z" />
</svg>

After

Width:  |  Height:  |  Size: 400 B

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M5.25 14.25h13.5m-13.5 0a3 3 0 0 1-3-3m3 3a3 3 0 1 0 0 6h13.5a3 3 0 1 0 0-6m-16.5-3a3 3 0 0 1 3-3h13.5a3 3 0 0 1 3 3m-19.5 0a4.5 4.5 0 0 1 .9-2.7L5.737 5.1a3.375 3.375 0 0 1 2.7-1.35h7.126c1.062 0 2.062.5 2.7 1.35l2.587 3.45a4.5 4.5 0 0 1 .9 2.7m0 0a3 3 0 0 1-3 3m0 3h.008v.008h-.008v-.008Zm0-6h.008v.008h-.008v-.008Zm-3 6h.008v.008h-.008v-.008Zm0-6h.008v.008h-.008v-.008Z" />
</svg>

After

Width:  |  Height:  |  Size: 573 B

4
public/icons/server.svg Normal file
View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M21.75 17.25v-.228a4.5 4.5 0 0 0-.12-1.03l-2.268-9.64a3.375 3.375 0 0 0-3.285-2.602H7.923a3.375 3.375 0 0 0-3.285 2.602l-2.268 9.64a4.5 4.5 0 0 0-.12 1.03v.228m19.5 0a3 3 0 0 1-3 3H5.25a3 3 0 0 1-3-3m19.5 0a3 3 0 0 0-3-3H5.25a3 3 0 0 0-3 3m16.5 0h.008v.008h-.008v-.008Zm-3 0h.008v.008h-.008v-.008Z" />
</svg>

After

Width:  |  Height:  |  Size: 498 B

4
public/icons/share.svg Normal file
View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M7.217 10.907a2.25 2.25 0 1 0 0 2.186m0-2.186c.18.324.283.696.283 1.093s-.103.77-.283 1.093m0-2.186 9.566-5.314m-9.566 7.5 9.566 5.314m0 0a2.25 2.25 0 1 0 3.935 2.186 2.25 2.25 0 0 0-3.935-2.186Zm0-12.814a2.25 2.25 0 1 0 3.933-2.185 2.25 2.25 0 0 0-3.933 2.185Z" />
</svg>

After

Width:  |  Height:  |  Size: 462 B

View File

@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
<path stroke-linecap="round" stroke-linejoin="round" d="M9 9.563C9 9.252 9.252 9 9.563 9h4.874c.311 0 .563.252.563.563v4.874c0 .311-.252.563-.563.563H9.564A.562.562 0 0 1 9 14.437V9.564Z" />
</svg>

After

Width:  |  Height:  |  Size: 431 B

4
public/icons/trash.svg Normal file
View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" />
</svg>

After

Width:  |  Height:  |  Size: 614 B

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M17.982 18.725A7.488 7.488 0 0 0 12 15.75a7.488 7.488 0 0 0-5.982 2.975m11.963 0a9 9 0 1 0-11.963 0m11.963 0A8.966 8.966 0 0 1 12 21a8.966 8.966 0 0 1-5.982-2.275M15 9.75a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
</svg>

After

Width:  |  Height:  |  Size: 400 B

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="m9.75 9.75 4.5 4.5m0-4.5-4.5 4.5M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
</svg>

After

Width:  |  Height:  |  Size: 270 B

7
view/_footer.gohtml Normal file
View File

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

10
view/_header.gohtml Normal file
View File

@ -0,0 +1,10 @@
{{ 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 }}

22
view/_index.gohtml Normal file
View File

@ -0,0 +1,22 @@
{{ 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 }}

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)
}

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

@ -0,0 +1,166 @@
{{ 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 }}

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)
}