2023-02-07 20:29:19 +00:00
|
|
|
|
|
|
|
import { createApp } from './vue.esm-browser.js';
|
|
|
|
|
|
|
|
import Cluster from './Cluster.js';
|
|
|
|
import Host from './Host.js';
|
|
|
|
|
|
|
|
createApp({
|
|
|
|
components: { Cluster, Host },
|
|
|
|
data() {
|
|
|
|
return {
|
|
|
|
forms: {
|
2023-09-10 14:47:54 +00:00
|
|
|
store: {},
|
2023-08-19 19:17:23 +00:00
|
|
|
storeUpload: {},
|
2023-09-10 14:47:54 +00:00
|
|
|
delKey: {},
|
2024-04-15 14:35:53 +00:00
|
|
|
hostFromTemplate: {},
|
|
|
|
hostFromTemplateDel: "",
|
2023-02-07 20:29:19 +00:00
|
|
|
},
|
|
|
|
session: {},
|
|
|
|
error: null,
|
|
|
|
publicState: null,
|
2023-11-04 12:53:00 +00:00
|
|
|
serverVersion: null,
|
2023-02-07 20:29:19 +00:00
|
|
|
uiHash: null,
|
|
|
|
watchingState: false,
|
|
|
|
state: null,
|
|
|
|
}
|
|
|
|
},
|
|
|
|
mounted() {
|
|
|
|
this.session = JSON.parse(sessionStorage.state || "{}")
|
|
|
|
this.watchPublicState()
|
|
|
|
},
|
|
|
|
watch: {
|
|
|
|
session: {
|
|
|
|
deep: true,
|
|
|
|
handler(v) {
|
|
|
|
sessionStorage.state = JSON.stringify(v)
|
|
|
|
|
|
|
|
if (v.token && !this.watchingState) {
|
|
|
|
this.watchState()
|
|
|
|
this.watchingState = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
publicState: {
|
|
|
|
deep: true,
|
|
|
|
handler(v) {
|
|
|
|
if (v) {
|
2023-11-04 12:53:00 +00:00
|
|
|
this.serverVersion = v.ServerVersion
|
2023-02-07 20:29:19 +00:00
|
|
|
if (this.uiHash && v.UIHash != this.uiHash) {
|
|
|
|
console.log("reloading")
|
|
|
|
location.reload()
|
|
|
|
} else {
|
|
|
|
this.uiHash = v.UIHash
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2024-04-15 14:35:53 +00:00
|
|
|
computed: {
|
|
|
|
hostsFromTemplate() {
|
|
|
|
return (this.state.Hosts||[]).filter((h) => h.Template)
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2023-02-07 20:29:19 +00:00
|
|
|
methods: {
|
|
|
|
copyText(text) {
|
|
|
|
event.preventDefault()
|
|
|
|
window.navigator.clipboard.writeText(text)
|
|
|
|
},
|
|
|
|
setToken() {
|
|
|
|
event.preventDefault()
|
|
|
|
this.session.token = this.forms.setToken
|
|
|
|
this.forms.setToken = null
|
|
|
|
},
|
2023-08-19 19:17:23 +00:00
|
|
|
uploadStore() {
|
|
|
|
event.preventDefault()
|
|
|
|
this.apiPost('/public/store.tar', this.$refs.storeUpload.files[0], (v) => {
|
|
|
|
this.forms.store = {}
|
|
|
|
}, "application/tar")
|
|
|
|
},
|
2023-09-10 14:47:54 +00:00
|
|
|
namedPassphrase(name, passphrase) {
|
|
|
|
return {Name: this.forms.store.name, Passphrase: btoa(this.forms.store.pass1)}
|
|
|
|
},
|
2023-02-13 12:03:42 +00:00
|
|
|
storeAddKey() {
|
2023-09-10 14:47:54 +00:00
|
|
|
this.apiPost('/store/add-key', this.namedPassphrase(), (v) => {
|
2023-02-13 12:03:42 +00:00
|
|
|
this.forms.store = {}
|
|
|
|
})
|
|
|
|
},
|
2023-09-10 14:47:54 +00:00
|
|
|
storeDelKey() {
|
2024-01-07 10:22:57 +00:00
|
|
|
event.preventDefault()
|
|
|
|
|
2023-09-10 14:47:54 +00:00
|
|
|
let name = this.forms.delKey.name
|
|
|
|
|
|
|
|
if (!confirm("Remove key named "+JSON.stringify(name)+"?")) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
this.apiPost('/store/delete-key', name , (v) => {
|
|
|
|
this.forms.delKey = {}
|
|
|
|
})
|
|
|
|
},
|
2023-02-07 20:29:19 +00:00
|
|
|
unlockStore() {
|
2023-09-10 14:47:54 +00:00
|
|
|
this.apiPost('/public/unlock-store', this.namedPassphrase(), (v) => {
|
2023-02-07 20:29:19 +00:00
|
|
|
this.forms.store = {}
|
|
|
|
|
|
|
|
if (v) {
|
|
|
|
this.session.token = v
|
|
|
|
if (!this.watchingState) {
|
|
|
|
this.watchState()
|
|
|
|
this.watchingState = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
},
|
2023-08-19 19:17:23 +00:00
|
|
|
uploadConfig() {
|
|
|
|
this.apiPost('/configs', this.$refs.configUpload.files[0], (v) => {}, "text/vnd.yaml")
|
|
|
|
},
|
2024-04-15 14:35:53 +00:00
|
|
|
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 = "" });
|
|
|
|
},
|
2023-08-19 19:17:23 +00:00
|
|
|
apiPost(action, data, onload, contentType = 'application/json') {
|
2023-02-07 20:29:19 +00:00
|
|
|
event.preventDefault()
|
|
|
|
|
|
|
|
if (data === undefined) {
|
|
|
|
throw("action " + action + ": no data")
|
|
|
|
}
|
|
|
|
|
|
|
|
/* TODO
|
|
|
|
fetch(action, {
|
|
|
|
method: 'POST',
|
|
|
|
body: JSON.stringify(data),
|
|
|
|
})
|
|
|
|
.then((response) => response.json())
|
|
|
|
.then((result) => onload)
|
|
|
|
// */
|
|
|
|
|
|
|
|
var xhr = new XMLHttpRequest()
|
|
|
|
|
|
|
|
xhr.responseType = 'json'
|
2023-08-19 19:17:23 +00:00
|
|
|
// TODO spinner, pending action notification, or something
|
2023-02-07 20:29:19 +00:00
|
|
|
xhr.onerror = () => {
|
|
|
|
// this.actionResults.splice(idx, 1, {...item, done: true, failed: true })
|
|
|
|
}
|
|
|
|
xhr.onload = (r) => {
|
|
|
|
if (xhr.status != 200) {
|
|
|
|
this.error = xhr.response
|
|
|
|
return
|
|
|
|
}
|
|
|
|
// this.actionResults.splice(idx, 1, {...item, done: true, resp: xhr.responseText})
|
|
|
|
this.error = null
|
|
|
|
if (onload) {
|
|
|
|
onload(xhr.response)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
xhr.open("POST", action)
|
|
|
|
xhr.setRequestHeader('Accept', 'application/json')
|
2023-08-19 19:17:23 +00:00
|
|
|
xhr.setRequestHeader('Content-Type', contentType)
|
2023-02-07 20:29:19 +00:00
|
|
|
if (this.session.token) {
|
|
|
|
xhr.setRequestHeader('Authorization', 'Bearer '+this.session.token)
|
|
|
|
}
|
2023-08-19 19:17:23 +00:00
|
|
|
|
|
|
|
if (contentType == "application/json") {
|
|
|
|
xhr.send(JSON.stringify(data))
|
|
|
|
} else {
|
|
|
|
xhr.send(data)
|
|
|
|
}
|
2023-02-07 20:29:19 +00:00
|
|
|
},
|
2024-04-15 14:35:53 +00:00
|
|
|
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()
|
|
|
|
},
|
2023-02-07 20:29:19 +00:00
|
|
|
download(url) {
|
|
|
|
event.target.target = '_blank'
|
|
|
|
event.target.href = this.downloadLink(url)
|
|
|
|
},
|
|
|
|
downloadLink(url) {
|
|
|
|
// TODO once-shot download link
|
|
|
|
return url + '?token=' + this.session.token
|
|
|
|
},
|
|
|
|
watchPublicState() {
|
|
|
|
this.watchStream('publicState', '/public-state')
|
|
|
|
},
|
|
|
|
watchState() {
|
|
|
|
this.watchStream('state', '/state', true)
|
|
|
|
},
|
|
|
|
watchStream(field, path, withToken) {
|
|
|
|
let evtSrc = new EventSource(path + (withToken ? '?token='+this.session.token : ''));
|
|
|
|
evtSrc.onmessage = (e) => {
|
|
|
|
let update = JSON.parse(e.data)
|
|
|
|
|
|
|
|
console.log("watch "+path+":", update)
|
|
|
|
|
|
|
|
if (update.err) {
|
|
|
|
console.log("watch error from server:", err)
|
|
|
|
}
|
|
|
|
if (update.set) {
|
|
|
|
this[field] = update.set
|
|
|
|
}
|
|
|
|
if (update.p) { // patch
|
|
|
|
new jsonpatch.JSONPatch(update.p, true).apply(this[field])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
evtSrc.onerror = (e) => {
|
|
|
|
// console.log("event source " + path + " error:", e)
|
|
|
|
if (evtSrc) evtSrc.close()
|
|
|
|
|
|
|
|
this[field] = null
|
|
|
|
|
|
|
|
window.setTimeout(() => { this.watchStream(field, path, withToken) }, 1000)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
}).mount('#app')
|
|
|
|
|