diff --git a/cmd/dkl-local-server/ws-hosts-from-templates.go b/cmd/dkl-local-server/ws-hosts-from-templates.go
index 04ad5a6..e808e0a 100644
--- a/cmd/dkl-local-server/ws-hosts-from-templates.go
+++ b/cmd/dkl-local-server/ws-hosts-from-templates.go
@@ -2,6 +2,7 @@ package main
import (
"log"
+ "net/netip"
"github.com/emicklei/go-restful"
@@ -55,6 +56,10 @@ func hostOrTemplate(cfg *localconfig.Config, name string) (host *localconfig.Hos
return
}
+func wsHostsFromTemplateList(req *restful.Request, resp *restful.Response) {
+ hostsFromTemplate.WsList(resp, "")
+}
+
func wsHostsFromTemplateSet(req *restful.Request, resp *restful.Response) {
name := req.PathParameter("name")
@@ -78,6 +83,10 @@ func wsHostsFromTemplateSet(req *restful.Request, resp *restful.Response) {
wsBadRequest(resp, "ip is required")
return
}
+ if _, err := netip.ParseAddr(v.IP); err != nil {
+ wsBadRequest(resp, "bad IP: "+err.Error())
+ return
+ }
found := false
for _, ht := range cfg.HostTemplates {
diff --git a/cmd/dkl-local-server/ws.go b/cmd/dkl-local-server/ws.go
index 81c2d4c..aed5f4e 100644
--- a/cmd/dkl-local-server/ws.go
+++ b/cmd/dkl-local-server/ws.go
@@ -76,6 +76,8 @@ func registerWS(rest *restful.Container) {
ws.Route(ws.GET("/clusters").To(wsListClusters).
Doc("List clusters"))
+ ws.Route(ws.GET("/hosts-from-template").To(wsHostsFromTemplateList).
+ Doc("List host template instances"))
ws.Route(ws.POST("/hosts-from-template/{name}").To(wsHostsFromTemplateSet).
Reads(HostFromTemplate{}).
Doc("Create or update a host template instance"))
diff --git a/html/ui/index.html b/html/ui/index.html
index e4ae229..97e3c86 100644
--- a/html/ui/index.html
+++ b/html/ui/index.html
@@ -87,11 +87,13 @@
Admin actions
+
Config
+
Store
Download
+
+
+ Hosts from template
+
+
+
+
diff --git a/html/ui/js/Host.js b/html/ui/js/Host.js
index 9f606ab..f811dad 100644
--- a/html/ui/js/Host.js
+++ b/html/ui/js/Host.js
@@ -10,7 +10,7 @@ export default {
Cluster: {{ host.Cluster }} ({{ host.Template }})
- {{ ip }}
+ {{ ip }}
Downloads
diff --git a/html/ui/js/app.js b/html/ui/js/app.js
index 8f78bdc..1994007 100644
--- a/html/ui/js/app.js
+++ b/html/ui/js/app.js
@@ -12,6 +12,8 @@ createApp({
store: {},
storeUpload: {},
delKey: {},
+ hostFromTemplate: {},
+ hostFromTemplateDel: "",
},
session: {},
error: null,
@@ -54,6 +56,12 @@ createApp({
}
},
+ computed: {
+ hostsFromTemplate() {
+ return (this.state.Hosts||[]).filter((h) => h.Template)
+ },
+ },
+
methods: {
copyText(text) {
event.preventDefault()
@@ -104,9 +112,21 @@ createApp({
})
},
uploadConfig() {
- event.preventDefault()
this.apiPost('/configs', this.$refs.configUpload.files[0], (v) => {}, "text/vnd.yaml")
},
+ hostFromTemplateAdd() {
+ let v = this.forms.hostFromTemplate;
+ this.apiPost('/hosts-from-template/'+v.name, v, (v) => { this.forms.hostFromTemplate = {} });
+ },
+ hostFromTemplateDel() {
+ event.preventDefault()
+
+ let v = this.forms.hostFromTemplateDel;
+ if (!confirm("delete host template instance "+v+"?")) {
+ return
+ }
+ this.apiDelete('/hosts-from-template/'+v, (v) => { this.forms.hostFromTemplateDel = "" });
+ },
apiPost(action, data, onload, contentType = 'application/json') {
event.preventDefault()
@@ -155,6 +175,27 @@ createApp({
xhr.send(data)
}
},
+ apiDelete(action, data, onload) {
+ event.preventDefault()
+
+ var xhr = new XMLHttpRequest()
+ xhr.onload = (r) => {
+ if (xhr.status != 200) {
+ this.error = xhr.response
+ return
+ }
+ this.error = null
+ if (onload) {
+ onload(xhr.response)
+ }
+ }
+ xhr.open("DELETE", action)
+ xhr.setRequestHeader('Accept', 'application/json')
+ if (this.session.token) {
+ xhr.setRequestHeader('Authorization', 'Bearer '+this.session.token)
+ }
+ xhr.send()
+ },
download(url) {
event.target.target = '_blank'
event.target.href = this.downloadLink(url)