diff --git a/script.go b/script.go
index 96af03b..2a8a27e 100644
--- a/script.go
+++ b/script.go
@@ -26,7 +26,7 @@ type Hook struct {
SSH string
}
-func GetScripts() (scripts []Script, err error) {
+func GetScripts() (scripts []Script, err error) {// {{{
scripts = []Script{}
var rows *sqlx.Rows
@@ -54,8 +54,8 @@ func GetScripts() (scripts []Script, err error) {
}
return
-}
-func UpdateScript(scriptID int, data []byte) (script Script, err error) {
+}// }}}
+func UpdateScript(scriptID int, data []byte) (script Script, err error) {// {{{
err = json.Unmarshal(data, &script)
if err != nil {
err = werr.Wrap(err)
@@ -106,12 +106,29 @@ func UpdateScript(scriptID int, data []byte) (script Script, err error) {
}
return
-}
-func DeleteScript(scriptID int) (err error) {
+}// }}}
+func DeleteScript(scriptID int) (err error) {// {{{
_, err = db.Exec(`DELETE FROM script WHERE id = $1`, scriptID)
if err != nil {
err = werr.Wrap(err)
return
}
return
-}
+}// }}}
+
+func UpdateHook(hook Hook) (err error) {// {{{
+ _, err = db.Exec(`UPDATE hook SET ssh=$2 WHERE id=$1`, hook.ID, strings.TrimSpace(hook.SSH))
+ if err != nil {
+ err = werr.Wrap(err)
+ return
+ }
+ return
+}// }}}
+func DeleteHook(hookID int) (err error) {// {{{
+ _, err = db.Exec(`DELETE FROM hook WHERE id=$1`, hookID)
+ if err != nil {
+ err = werr.Wrap(err)
+ return
+ }
+ return
+}// }}}
diff --git a/sql/0012.sql b/sql/0012.sql
new file mode 100644
index 0000000..1297758
--- /dev/null
+++ b/sql/0012.sql
@@ -0,0 +1 @@
+ALTER TABLE public.hook ADD CONSTRAINT hook_unique UNIQUE (node_id,script_id);
diff --git a/static/js/app.mjs b/static/js/app.mjs
index 503ffc4..fac5cfe 100644
--- a/static/js/app.mjs
+++ b/static/js/app.mjs
@@ -973,11 +973,18 @@ class ConnectedNode {
}
class ScriptHooks extends Component {
- constructor(hooks) {
+ constructor(hooks) {// {{{
super()
this.hooks = hooks
- }
- renderComponent() {
+ this.scriptGrid = null
+
+ mbus.subscribe('hook_deleted', event => {
+ const deletedHook = event.detail
+ this.hooks = this.hooks.filter(h => h.ID !== deletedHook.ID)
+ this.renderHooks()
+ })
+ }// }}}
+ renderComponent() {// {{{
const div = document.createElement('div')
div.innerHTML = `
Script hooks
@@ -988,24 +995,30 @@ class ScriptHooks extends Component {
`
- div.querySelector('.add').addEventListener('click', ()=>{
+ div.querySelector('.add').addEventListener('click', () => {
alert('FIXME')
})
- const scriptsGrid = div.querySelector('.scripts-grid')
- for(const hook of this.hooks) {
- const h = new ScriptHook(hook)
- scriptsGrid.append(h.render())
- }
+ this.scriptGrid = div.querySelector('.scripts-grid')
+ this.renderHooks()
return div.children
- }
+ }// }}}
+ renderHooks() {// {{{
+ this.scriptGrid.innerHTML = ''
+
+ for (const hook of this.hooks) {
+ const h = new ScriptHook(hook)
+ this.scriptGrid.append(h.render())
+ }
+ }// }}}
}
class ScriptHook extends Component {
constructor(hook) {// {{{
super()
this.hook = hook
+ this.element_ssh = null
}// }}}
renderComponent() {// {{{
const tmpl = document.createElement('template')
@@ -1015,14 +1028,57 @@ class ScriptHook extends Component {
${this.hook.SSH}
`
+ this.element_ssh = tmpl.content.querySelector('.script-ssh')
- tmpl.content.querySelector('.script-ssh').addEventListener('click', () => {
- prompt('SSH', this.hook.SSH)
- //new ConnectionDataDialog(this.hook, () => _app.edit(_app.currentNode.ID)).render()
- })
+ tmpl.content.querySelector('.script-ssh').addEventListener('click', () => this.update())
+ tmpl.content.querySelector('.script-unhook').addEventListener('click', () => this.delete())
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.element_ssh.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
+ }
+ mbus.dispatch('hook_deleted', this.hook)
+ })
+ .catch(err => showError(err))
+ }// }}}
}
class ScriptsList extends Component {
diff --git a/webserver.go b/webserver.go
index cd8d9b3..1b6e90e 100644
--- a/webserver.go
+++ b/webserver.go
@@ -50,7 +50,8 @@ func initWebserver() (err error) {
http.HandleFunc("/scripts/", actionScripts)
http.HandleFunc("/scripts/update/{scriptID}", actionScriptUpdate)
http.HandleFunc("/scripts/delete/{scriptID}", actionScriptDelete)
- http.HandleFunc("/hooks/update/{hookID}", actionHookUpdate)
+ http.HandleFunc("/hooks/update", actionHookUpdate)
+ http.HandleFunc("/hooks/delete/{hookID}", actionHookDelete)
err = http.ListenAndServe(address, nil)
return
@@ -623,13 +624,36 @@ func actionScriptDelete(w http.ResponseWriter, r *http.Request) { // {{{
} // }}}
func actionHookUpdate(w http.ResponseWriter, r *http.Request) { // {{{
+ var hook Hook
+ body, _ := io.ReadAll(r.Body)
+ err := json.Unmarshal(body, &hook)
+ if err != nil {
+ err = werr.Wrap(err)
+ httpError(w, err)
+ return
+ }
+
+ err = UpdateHook(hook)
+ if err != nil {
+ err = werr.Wrap(err)
+ httpError(w, err)
+ return
+ }
+
+ out := struct {
+ OK bool
+ }{
+ true,
+ }
+ j, _ := json.Marshal(out)
+ w.Write(j)
+} // }}}
+func actionHookDelete(w http.ResponseWriter, r *http.Request) { // {{{
hookID := 0
hookIDStr := r.PathValue("hookID")
hookID, _ = strconv.Atoi(hookIDStr)
- // XXX - here
-
- err := UpdateHook(hook)
+ err := DeleteHook(hookID)
if err != nil {
err = werr.Wrap(err)
httpError(w, err)