diff --git a/node.go b/node.go index 85738d6..aa83ffe 100644 --- a/node.go +++ b/node.go @@ -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 diff --git a/script.go b/script.go index e8f98ab..237f23a 100644 --- a/script.go +++ b/script.go @@ -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, '')`, nodeID, scriptID) + _, err = db.Exec(`INSERT INTO hook(node_id, script_id, ssh) VALUES($1, $2, '')`, 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 diff --git a/static/css/main.css b/static/css/main.css index ed55cbb..31d6281 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -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; +} diff --git a/static/js/app.mjs b/static/js/app.mjs index 9eaf8a7..994c592 100644 --- a/static/js/app.mjs +++ b/static/js/app.mjs @@ -1096,62 +1096,24 @@ class ScriptHook extends Component { tmpl.innerHTML = `
${this.hook.Script.Name}
-
` + + 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 = ` +
+
+ +
+
SSH
+
+ +
+ Environment +
+
A map with keys and values as strings.
+ +
+
+ ` + + 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 diff --git a/static/less/main.less b/static/less/main.less index c9a29bc..fdf99b6 100644 --- a/static/less/main.less +++ b/static/less/main.less @@ -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; + } +}