Rearranged script hook UI

This commit is contained in:
Magnus Åhall 2025-08-08 15:36:32 +02:00
parent 5145830f65
commit 2cab8c3ddb
5 changed files with 177 additions and 50 deletions

View file

@ -93,7 +93,8 @@ func GetNode(nodeID int) (node Node, err error) { // {{{
SELECT
h.id,
to_jsonb(s) AS script,
ssh
ssh,
env
FROM hook h
INNER JOIN public.script s ON h.script_id = s.id
WHERE

View file

@ -162,7 +162,7 @@ func SearchScripts(search string) (scripts []Script, err error) { // {{{
return
} // }}}
func HookScript(nodeID, scriptID int) (err error) { // {{{
_, err = db.Exec(`INSERT INTO hook(node_id, script_id, ssh, env) VALUES($1, $2, '<host>')`, nodeID, scriptID)
_, err = db.Exec(`INSERT INTO hook(node_id, script_id, ssh) VALUES($1, $2, '<host>')`, nodeID, scriptID)
return
} // }}}
@ -197,7 +197,8 @@ func GetHook(hookID int) (hook Hook, err error) { // {{{
return
} // }}}
func UpdateHook(hook Hook) (err error) { // {{{
_, err = db.Exec(`UPDATE hook SET ssh=$2 WHERE id=$1`, hook.ID, strings.TrimSpace(hook.SSH))
j, _ := json.Marshal(hook.Env)
_, err = db.Exec(`UPDATE hook SET ssh=$2, env=$3 WHERE id=$1`, hook.ID, strings.TrimSpace(hook.SSH), j)
if err != nil {
err = werr.Wrap(err)
return

View file

@ -276,7 +276,7 @@ select:focus {
}
#script-hooks .scripts-grid .script-group {
display: grid;
grid-template-columns: repeat(4, min-content);
grid-template-columns: repeat(3, min-content);
align-items: center;
grid-gap: 2px 0px;
padding: 16px;
@ -296,6 +296,7 @@ select:focus {
#script-hooks .scripts-grid .script-ssh {
margin-right: 16px;
}
#script-hooks .scripts-grid .script-name,
#script-hooks .scripts-grid .script-ssh {
cursor: pointer;
color: #555;
@ -519,3 +520,31 @@ dialog#connection-data div.button {
cursor: pointer;
margin-top: 4px;
}
#script-hook-dialog {
display: grid;
grid-gap: 8px;
padding: 32px;
}
#script-hook-dialog .top {
display: grid;
grid-template-columns: 1fr min-content;
grid-gap: 16px;
}
#script-hook-dialog .top .header {
font-size: 1.25em;
font-weight: bold;
color: var(--section-color);
}
#script-hook-dialog .top img {
height: 32px;
cursor: pointer;
}
#script-hook-dialog .label {
font-weight: bold;
margin-top: 16px;
}
#script-hook-dialog textarea {
height: 50vh;
width: 50vw;
font-family: monospace;
}

View file

@ -1096,62 +1096,24 @@ class ScriptHook extends Component {
tmpl.innerHTML = `
<div class="script-name">${this.hook.Script.Name}</div>
<div class="script-ssh"></div>
<div class="script-unhook"><img src="/images/${_VERSION}/node_modules/@mdi/svg/svg/trash-can.svg" /></div>
<div class="script-schedule"><img src="/images/${_VERSION}/node_modules/@mdi/svg/svg/play-box.svg" /></div>
`
this.elementSchedule = tmpl.content.querySelector('.script-schedule')
this.elementSSH = tmpl.content.querySelector('.script-ssh')
this.elementSSH.innerText = `[ ${this.hook.SSH} ]`
tmpl.content.querySelector('.script-name').addEventListener('click', () => this.update())
tmpl.content.querySelector('.script-ssh').addEventListener('click', () => this.update())
tmpl.content.querySelector('.script-unhook').addEventListener('click', () => this.delete())
tmpl.content.querySelector('.script-schedule').addEventListener('click', () => this.run())
return tmpl.content
}// }}}
update() {// {{{
const ssh = prompt('SSH', this.hook.SSH)
if (ssh === null)
return
if (ssh.trim() === '') {
alert(`SSH can't be empty.`)
return
}
const request = {
ID: this.hook.ID,
SSH: ssh,
}
fetch('/hooks/update', {
method: 'POST',
body: JSON.stringify(request),
})
.then(data => data.json())
.then(json => {
if (!json.OK) {
showError(json.Error)
return
}
this.hook.SSH = ssh
this.elementSSH.innerText = this.hook.SSH
})
.catch(err => showError(err))
}// }}}
delete() {// {{{
if (!confirm(`Unhook the '${this.hook.Script.Name}' script?`))
return
fetch(`/hooks/delete/${this.hook.ID}`)
.then(data => data.json())
.then(json => {
if (!json.OK) {
showError(json.Error)
return
}
this.parentList.hookDeleted(this.hook.ID)
})
.catch(err => showError(err))
new ScriptHookDialog(this.hook, this.parentList, () => {
this.elementSSH.innerText = `[ ${this.hook.SSH} ]`
}).render()
}// }}}
run() {// {{{
if (this.scheduleDisable)
@ -1470,4 +1432,104 @@ class ScriptSelectDialog extends Component {
}// }}}
}
class ScriptHookDialog extends Component {
constructor(hook, parentList, callback) {// {{{
super()
this.hook = hook
this.callback = callback
this.parentList = parentList
this.dlg = document.createElement('dialog')
this.dlg.id = 'script-hook-dialog'
this.dlg.addEventListener('close', () => this.dlg.remove())
this.env = null
this.ssh = null
}// }}}
renderComponent() {// {{{
const div = document.createElement('div')
div.innerHTML = `
<div class="top">
<div class="header"></div>
<img src="/images/${_VERSION}/node_modules/@mdi/svg/svg/trash-can.svg">
</div>
<div class="label">SSH</div>
<div><input type="text" class="ssh" style="width: 100%" /></div>
<div class="label">
Environment
</div>
<div style="font-size: 0.9em">A map with keys and values as strings.</div>
<div><textarea class="env"></textarea></div>
<div><button>Update</button></div>
`
div.querySelector('.header').innerText = `Hook for ${this.hook.Script.Name}`
div.querySelector('.top img').addEventListener('click', () => this.delete())
this.ssh = div.querySelector('.ssh')
this.ssh.value = this.hook.SSH
this.ssh.addEventListener('keydown', event => {
if (event.key == 'Enter') {
event.stopPropagation()
this.save()
}
})
this.env = div.querySelector('.env')
this.env.value = JSON.stringify(this.hook.Env, null, " ")
const button = div.querySelector('button')
this.env.addEventListener('keydown', event => {
if (event.ctrlKey && event.key == 's') {
event.preventDefault()
event.stopPropagation()
this.save()
}
})
button.addEventListener('click', () => this.save())
this.dlg.append(...div.children)
document.body.append(this.dlg)
this.dlg.showModal()
return []
}// }}}
save() {// {{{
if (this.ssh.value.trim() === '') {
alert('SSH has to be filled in.')
return
}
if (this.env.value.trim() === '')
this.env.value = '{}'
try {
this.hook.Env = JSON.parse(this.env.value)
this.hook.SSH = this.ssh.value.trim()
window._app.query('/hooks/update', this.hook)
.then(() => {
this.callback()
this.dlg.close()
})
.catch(err => showError(err))
} catch (err) {
alert(`A JSON error occured:\n\n${err}`)
}
}// }}}
delete() {// {{{
if (!confirm(`Unhook the '${this.hook.Script.Name}' script?`))
return
window._app.query(`/hooks/delete/${this.hook.ID}`)
.then(() => {
this.dlg.close()
this.parentList.hookDeleted(this.hook.ID)
})
.catch(err => showError(err))
}// }}}
}
// vim: foldmethod=marker

View file

@ -365,7 +365,7 @@ select:focus {
.script-group {
display: grid;
grid-template-columns: repeat(4, min-content);
grid-template-columns: repeat(3, min-content);
align-items: center;
grid-gap: 2px 0px;
padding: 16px;
@ -389,7 +389,7 @@ select:focus {
margin-right: 16px;
}
.script-ssh {
.script-name, .script-ssh {
cursor: pointer;
color: #555;
}
@ -666,3 +666,37 @@ dialog#connection-data {
}
}
}
#script-hook-dialog {
display: grid;
grid-gap: 8px;
padding: 32px;
.top {
display: grid;
grid-template-columns: 1fr min-content;
grid-gap: 16px;
.header {
font-size: 1.25em;
font-weight: bold;
color: var(--section-color);
}
img {
height: 32px;
cursor: pointer;
}
}
.label {
font-weight: bold;
margin-top: 16px;
}
textarea {
height: 50vh;
width: 50vw;
font-family: monospace;
}
}