diff --git a/node.go b/node.go index aa83ffe..2bf6302 100644 --- a/node.go +++ b/node.go @@ -94,7 +94,8 @@ func GetNode(nodeID int) (node Node, err error) { // {{{ h.id, to_jsonb(s) AS script, ssh, - env + env, + schedule_on_child_update AS ScheduleOnChildUpdate FROM hook h INNER JOIN public.script s ON h.script_id = s.id WHERE diff --git a/script.go b/script.go index f93b199..ce7a8d0 100644 --- a/script.go +++ b/script.go @@ -23,11 +23,12 @@ type Script struct { } type Hook struct { - ID int - Node Node - Script Script - SSH string - Env map[string]string + ID int + Node Node + Script Script + SSH string + Env map[string]string + ScheduleOnChildUpdate bool `db:"schedule_on_child_update"` } func GetScript(scriptID int) (script Script, err error) { // {{{ @@ -135,13 +136,20 @@ func SearchScripts(search string) (scripts []Script, err error) { // {{{ row := db.QueryRow(` SELECT - json_agg(script) AS scripts - FROM public.script - WHERE - name ILIKE $1 - ORDER BY - "group" ASC, - name ASC + COALESCE( + json_agg(scripts), + '[]'::json + ) AS scripts + FROM ( + SELECT + to_json(script) AS scripts + FROM public.script + WHERE + name ILIKE $1 + ORDER BY + "group" ASC, + name ASC + ) scripts `, search, ) @@ -172,11 +180,12 @@ func GetHook(hookID int) (hook Hook, err error) { // {{{ to_json(res) FROM ( SELECT - h.id, - h.ssh, - h.env, - (SELECT to_json(node) FROM node WHERE id = h.node_id) AS node, - (SELECT to_json(script) FROM script WHERE id = h.script_id) AS script + h.id, + h.ssh, + h.env, + h.schedule_on_child_update, + (SELECT to_json(node) FROM node WHERE id = h.node_id) AS node, + (SELECT to_json(script) FROM script WHERE id = h.script_id) AS script FROM hook h WHERE h.id = $1 @@ -198,7 +207,20 @@ func GetHook(hookID int) (hook Hook, err error) { // {{{ } // }}} func UpdateHook(hook Hook) (err error) { // {{{ 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) + _, err = db.Exec(` + UPDATE hook + SET + ssh=$2, + env=$3, + schedule_on_child_update=$4 + WHERE + id=$1 + `, + hook.ID, + strings.TrimSpace(hook.SSH), + j, + hook.ScheduleOnChildUpdate, + ) if err != nil { err = werr.Wrap(err) return @@ -259,6 +281,58 @@ func ScheduleHook(hookID int) (err error) { // {{{ return } // }}} +func ScheduleHookRecursivelyUpwards(nodeID int) (err error) { // {{{ + var rows *sql.Rows + rows, err = db.Query(` + WITH RECURSIVE rec AS ( + SELECT + id, + parent_id + FROM public.node + WHERE + id = $1 + + UNION ALL + + SELECT + n.id, + n.parent_id + FROM node n + INNER JOIN rec ON n.id = rec.parent_id + ) + + SELECT + hook.id + FROM rec + INNER JOIN hook ON + hook.node_id = rec.id AND + hook.schedule_on_child_update + `, + nodeID, + ) + if err != nil { + err = werr.Wrap(err) + return + } + defer rows.Close() + + for rows.Next() { + var hookID int + err = rows.Scan(&hookID) + if err != nil { + err = werr.Wrap(err) + return + } + + err = ScheduleHook(hookID) + if err != nil { + err = werr.Wrap(err) + return + } + } + + return +} // }}} func ScriptPreservedID(name, source string) (id int, err error) { // {{{ sum := md5.Sum([]byte(source)) diff --git a/sql/0018.sql b/sql/0018.sql new file mode 100644 index 0000000..f4617ac --- /dev/null +++ b/sql/0018.sql @@ -0,0 +1 @@ +ALTER TABLE public.hook DROP CONSTRAINT hook_unique; diff --git a/sql/0019.sql b/sql/0019.sql new file mode 100644 index 0000000..b9cad16 --- /dev/null +++ b/sql/0019.sql @@ -0,0 +1 @@ +ALTER TABLE public.hook ADD schedule_on_child_update bool DEFAULT false NOT NULL; diff --git a/static/js/app.mjs b/static/js/app.mjs index b449705..dba1c17 100644 --- a/static/js/app.mjs +++ b/static/js/app.mjs @@ -1464,6 +1464,7 @@ class ScriptHookDialog extends Component { this.env = null this.ssh = null + this.schedule_on_child_update = null }// }}} renderComponent() {// {{{ const div = document.createElement('div') @@ -1472,9 +1473,16 @@ class ScriptHookDialog extends Component {
+
SSH
+
Schedule automatically
+
+ + +
+
Environment
@@ -1499,6 +1507,9 @@ class ScriptHookDialog extends Component { this.env = div.querySelector('.env') this.env.value = JSON.stringify(this.hook.Env, null, " ") + this.schedule_on_child_update = div.querySelector('.schedule-on-child') + this.schedule_on_child_update.checked = this.hook.ScheduleOnChildUpdate + const button = div.querySelector('button') this.env.addEventListener('keydown', event => { if (event.ctrlKey && event.key == 's') { @@ -1527,6 +1538,7 @@ class ScriptHookDialog extends Component { try { this.hook.Env = JSON.parse(this.env.value) this.hook.SSH = this.ssh.value.trim() + this.hook.ScheduleOnChildUpdate = this.schedule_on_child_update.checked window._app.query('/hooks/update', this.hook) .then(() => { this.callback() @@ -1659,7 +1671,7 @@ class ScriptExecutionValueDialog extends Component { this.value = null }// }}} - getValue(execution) { + getValue(execution) {// {{{ switch (this.valueName) { case 'Source': return execution.Source @@ -1677,7 +1689,7 @@ class ScriptExecutionValueDialog extends Component { case 'OutputStderr': return execution[this.valueName]?.String } - } + }// }}} renderComponent() {// {{{ const div = document.createElement('div') div.innerHTML = ` @@ -1710,5 +1722,4 @@ class ScriptExecutionValueDialog extends Component { }// }}} } - // vim: foldmethod=marker diff --git a/webserver.go b/webserver.go index 2cded8b..1a42bb7 100644 --- a/webserver.go +++ b/webserver.go @@ -51,7 +51,7 @@ func initWebserver() (err error) { http.HandleFunc("/scripts/", actionScripts) http.HandleFunc("/scripts/update/{scriptID}", actionScriptUpdate) http.HandleFunc("/scripts/delete/{scriptID}", actionScriptDelete) - http.HandleFunc("/hooks/search", actionScriptsSearch) + http.HandleFunc("/scripts/search", actionScriptsSearch) http.HandleFunc("/hooks/update", actionHookUpdate) http.HandleFunc("/hooks/delete/{hookID}", actionHookDelete) http.HandleFunc("/hooks/schedule/{hookID}", actionHookSchedule) @@ -173,6 +173,14 @@ func actionNodeUpdate(w http.ResponseWriter, r *http.Request) { // {{{ return } + // Recursively schedule hooks with automatic trigger. + err = ScheduleHookRecursivelyUpwards(nodeID) + if err != nil { + err = werr.Wrap(err) + httpError(w, err) + return + } + out := struct { OK bool }{ @@ -605,7 +613,7 @@ func actionScripts(w http.ResponseWriter, r *http.Request) { // {{{ } out := struct { - OK bool + OK bool Scripts []Script }{ true, @@ -629,7 +637,7 @@ func actionScriptUpdate(w http.ResponseWriter, r *http.Request) { // {{{ } out := struct { - OK bool + OK bool Script Script }{ true, @@ -678,7 +686,7 @@ func actionScriptsSearch(w http.ResponseWriter, r *http.Request) { // {{{ } out := struct { - OK bool + OK bool Scripts []Script }{ true, @@ -763,7 +771,7 @@ func actionScriptExecutions(w http.ResponseWriter, r *http.Request) { // {{{ } out := struct { - OK bool + OK bool ScriptExecutions []ScriptExecutionBrief }{ true, @@ -785,7 +793,7 @@ func actionScriptExecutionGet(w http.ResponseWriter, r *http.Request) { // {{{ } out := struct { - OK bool + OK bool ScriptExecution ScriptExecution }{ true,