@inteli.city/node-red-contrib-exec-collection 1.0.3 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/exec.service.html CHANGED
@@ -1,311 +1,424 @@
1
1
  <!--* header -->
2
2
  <script type="text/markdown" data-help-name="exec.service">
3
- ## Overview
4
- Runs system commands on a queue and returns its output.
5
-
6
- ## How to use
7
- The template text on this node works exactly the same as the template node. On the `Command` input it allows you to use the command line. The template text written is a file called `$file`. Below there are some possible commands to run with it (considering these commands are installed on the system):
8
-
9
- * `bash $file`
10
- * `node $file`
11
- * `python3 $file`
12
- * `python3 -u $file` (spawn)
13
- * `Rscript $file`
14
- * `awk -f $file`
15
- * `psql postgresql://user:passwds@host:port/database -f $file`
16
- * `ogr2ogr -f GEOJSON /vsistdout/ PG:"host=host port=portnumber user=user password=password dbname=dbname" -sql "@$file"`
17
- * `ogr2ogr -f GEOJSON /vsistdout/ WFS:"http://wfspage" layername -sql "@$file"`
18
- * `cat $file | ssh -i /path/key user@ip bash -s`
19
- * `cat $file | ssh -i /path/key user@ip node -s`
20
- * `cat $file | ssh -i /path/key user@ip python3 -s`
21
- * `mv $file $file.mjs; node $file.mjs; rm $file.mjs`
22
- * `/usr/bin/time bash $file`
23
- * `valgrind bash $file`
24
-
25
- See more examples [here](https://www.npmjs.com/package/node-red-contrib-exec.service).
3
+ ## exec.service
4
+
5
+ Runs a shell command as a persistent service. stdout is streamed continuously as output messages. The process is restarted automatically on failure.
6
+
7
+ **Mental model:** you are managing a daemon not a one-shot command. The process stays alive and keeps emitting output.
8
+
9
+ ---
10
+
11
+ ## Command
12
+
13
+ Any shell command that runs continuously:
14
+
15
+ ```
16
+ tail -f /var/log/syslog
17
+ python3 /opt/services/listener.py
18
+ node /opt/services/worker.js
19
+ ffmpeg -i rtsp://... -f ...
20
+ ```
21
+
22
+ The command is executed via `/bin/bash -c` (Linux) or `cmd.exe /c` (Windows).
23
+
24
+ ---
25
+
26
+ ## Template and `$file`
27
+
28
+ Write code in the **Template** editor and reference it with `$file` in the command:
29
+
30
+ ```
31
+ python3 $file
32
+ node $file
33
+ bash $file
34
+ Rscript $file
35
+ ```
36
+
37
+ The template is rendered with Nunjucks before the process starts and written to a temporary file. Use `{{ flow.get('key') }}`, `{{ global.get('key') }}`, or `{{ env.MY_VAR }}` to inject dynamic values. The template re-renders on every restart.
38
+
39
+ ```python
40
+ # Example: Python service using a flow variable
41
+ import time, json
42
+
43
+ interval = {{ flow.get('pollInterval') or 5 }}
44
+
45
+ while True:
46
+ print(json.dumps({"tick": True}))
47
+ time.sleep(interval)
48
+ ```
49
+
50
+ ---
51
+
52
+ ## Restart behavior
53
+
54
+ When the process exits unexpectedly, the node automatically restarts it after the configured delay.
55
+
56
+ | Field | Default | Description |
57
+ |---|---|---|
58
+ | Restart delay | 1000 ms | Wait before restarting |
59
+ | Max retries | 0 | Maximum restart attempts (0 = infinite) |
60
+
61
+ If the process runs stably for 10 seconds, the retry counter resets.
62
+
63
+ Set **Max retries > 0** to stop after N failures (e.g. to avoid looping on a bad command).
64
+
65
+ ---
66
+
67
+ ## Status
68
+
69
+ | Badge | Meaning |
70
+ |---|---|
71
+ | Blue ring `running` | Process is alive and streaming |
72
+ | Yellow ring `restarting (retry N)` | Waiting to restart after crash |
73
+ | Grey dot `stopped` | Manually killed or max retries exceeded |
74
+
75
+ ---
76
+
77
+ ## Stop / Kill
78
+
79
+ Send `msg.stop = true` to stop the service without redeploying.
80
+
81
+ The **⏹ button** in the node editor header kills the process immediately. A confirmation dialog appears when the service is running or restarting.
82
+
83
+ ---
84
+
85
+ ## Output
86
+
87
+ stdout is streamed using the same parsing system as `python.queue` and `node.queue`.
88
+
89
+ **Parsing: Delimited** (default) — buffers output and splits on the delimiter (`\n` by default). Each line produces one message.
90
+
91
+ **Parsing: Raw** — emits each stdout chunk immediately with no buffering. Chunks may not align with line boundaries.
92
+
93
+ **Output format** — how each segment is parsed: Plain text, JSON, YAML, or XML.
94
+
95
+ stderr lines become node warnings — they do not appear in `msg.payload`.
96
+
97
+ ---
98
+
99
+ [Full documentation](https://www.npmjs.com/package/@inteli.city/node-red-contrib-exec-collection)
26
100
  </script>
101
+
102
+ <!--* styles -->
103
+ <style>
104
+ #node-es-restart-btn:hover { color: #337ab7 !important; }
105
+ #node-es-kill-btn:hover { color: #d9534f !important; }
106
+ #node-es-kill-btn.is-running { color: #d9534f !important; }
107
+ </style>
108
+
27
109
  <!--* node-design -->
28
110
  <script type="text/html" data-template-name="exec.service">
29
- <div class="form-row">
111
+ <div class="form-row" style="position:relative;">
30
112
  <label for="node-input-name">
31
- <i class="fa fa-tag"></i>
32
- <span data-i18n="node-red:common.label.name"></span>
33
- </label>
34
-
35
- <div style="display: inline-block; width: calc(100% - 105px)">
36
- <input type="text" id="node-input-name" data-i18n="[placeholder]node-red:common.label.name">
37
- </div>
38
-
113
+ <i class="fa fa-tag"></i>
114
+ <span data-i18n="node-red:common.label.name"></span>
115
+ </label>
116
+ <div style="display: inline-block; width: calc(100% - 165px)">
117
+ <input type="text" id="node-input-name" style="width:100%;" data-i18n="[placeholder]node-red:common.label.name">
118
+ </div>
119
+ <button id="node-es-restart-btn"
120
+ title="Restart service"
121
+ style="position:absolute; right:32px; top:50%; transform:translateY(-50%); z-index:5;
122
+ background:transparent; border:none; padding:6px 8px; cursor:pointer;
123
+ color:#888; font-size:13px; line-height:1;">
124
+ <i class="fa fa-refresh"></i>
125
+ </button>
126
+ <button id="node-es-kill-btn"
127
+ title="Kill service"
128
+ style="position:absolute; right:5px; top:50%; transform:translateY(-50%); z-index:5;
129
+ background:transparent; border:none; padding:6px 8px; cursor:pointer;
130
+ color:#888; font-size:13px; line-height:1;">
131
+ <i class="fa fa-stop"></i>
132
+ </button>
39
133
  </div>
40
134
 
41
135
  <div class="form-row">
42
136
  <label for="node-input-command">
43
- <i class="fa fa-file"></i>
44
- <span data-i18n="node-red:exec.label.command"></span>
45
- </label>
46
- <input style="display: inline-block; width: calc(100% - 108px);" type="text" id="node-input-command" data-i18n="[placeholder]node-red:exec.label.command">
137
+ <i class="fa fa-terminal"></i>
138
+ <span>Command</span>
139
+ </label>
140
+ <input type="text" id="node-input-command" style="display:inline-block; width:calc(100% - 108px);" placeholder="e.g. tail -f /var/log/syslog" list="exec-service-command-suggestions">
141
+ <datalist id="exec-service-command-suggestions"></datalist>
142
+ </div>
47
143
 
144
+ <div class="form-row">
145
+ <label><i class="fa fa-clock-o"></i> <span>Restart</span></label>
146
+ <input type="number" id="node-input-restartDelay" style="width:65px;">
147
+ <span style="font-size:0.85em; color:#888; margin-left:3px;">ms</span>
148
+ <label style="margin-left:15px;"><i class="fa fa-repeat"></i> <span>Max retries</span></label>
149
+ <input type="number" id="node-input-maxRetries" style="width:55px; margin-left:0;">
150
+ <span style="font-size:0.75em; color:#888; margin-left:4px;">(0 = ∞)</span>
48
151
  </div>
49
152
 
50
- <div class="form-row" style="position: relative;">
153
+ <div class="form-row" style="position:relative;">
51
154
  <label>
52
- <i class="fa fa-sign-out"></i>
53
- <span>Queue</span>
54
- </label>
55
- <input style="width:60px" type="number" id="node-input-queue" value="1">
56
-
57
- <div style="position: absolute; right:0;display:inline-block; text-align: right; font-size: 0.8em;">
58
-
59
- <span style="margin-right:10px;font-size:9px;">No Input</span>
60
- <input type="checkbox" id="node-input-inputEmpty" style="display:inline-block; width:auto;margin-right:30px;">
61
-
62
- <span style="margin-right:10px;font-size:9px;">Cmd Template</span>
63
- <!-- <input type="checkbox" id="node-input-addpayCB" style="display:inline-block; width:auto;margin-right:30px;"> -->
64
- <input type="checkbox" id="node-input-cmdTemplate" style="display:inline-block; width:auto;margin-right:30px;">
65
-
66
- <input type="hidden" id="node-input-template" autofocus="autofocus">
67
-
68
- <span style="font-size:9px;" data-i18n="node-red:template.label.format"></span>:
69
- <select id="node-input-format" style="width:110px; font-size: 10px !important; height: 24px; padding:0;">
70
- <option value="handlebars">Mustache</option>
71
- <option value="javascript">Javascript</option>
155
+ <i class="fa fa-code"></i>
156
+ <span>Template</span>
157
+ </label>
158
+ <div style="position:absolute; right:0; display:inline-block; text-align:right; font-size:0.8em;">
159
+ <input type="hidden" id="node-input-template">
160
+ <select id="node-input-format" style="width:90px; font-size:10px !important; height:24px; padding:0;">
72
161
  <option value="python">Python</option>
73
- <option value="sh">Shell</option>
74
- <option value="r">R</option>
75
- <option value="pgsql">PGSQL</option>
76
- <option value="nginx">NGINX</option>
77
- <option value="apache_conf">Apache</option>
78
- <option value="dockerfile">Dockerfile</option>
79
- <option value="terraform">Terraform</option>
80
- <option value="text" data-i18n="node-red:template.label.none"></option>
162
+ <option value="javascript">JavaScript</option>
163
+ <option value="sh">Shell</option>
164
+ <option value="text">Text</option>
81
165
  </select>
82
- <button id="node-template-expand-editor" class="red-ui-button red-ui-button-small"><i class="fa fa-expand"></i></button>
166
+ <button id="node-es-template-expand-editor" class="red-ui-button red-ui-button-small"><i class="fa fa-expand"></i></button>
83
167
  </div>
84
168
  </div>
85
169
  <div class="form-row node-text-editor-row">
86
- <div style="height: 250px; min-height:150px;" class="node-text-editor" id="node-input-template-editor" ></div>
170
+ <div style="height:250px; min-height:150px;" class="node-text-editor" id="node-input-template-editor"></div>
87
171
  </div>
88
172
 
89
- <div class="form-row" style="margin-bottom:0px;">
173
+ <div class="form-row" style="margin-bottom:0px; white-space:nowrap;">
90
174
  <label for="node-input-output"><i class="fa fa-long-arrow-right"></i> <span data-i18n="node-red:template.label.output"></span></label>
91
- <select id="node-input-output" style="width:170px;">
175
+ <select id="node-input-output" style="width:90px;">
92
176
  <option value="str">Plain text</option>
93
177
  <option value="parsedJSON">Parsed JSON</option>
94
178
  <option value="parsedYAML">Parsed YAML</option>
95
179
  <option value="parsedXML">Parsed XML</option>
96
180
  </select>
97
181
 
182
+ <label style="margin-left:14px;" for="node-input-field"><i class="fa fa-ellipsis-h"></i> <span data-i18n="node-red:common.label.property"></span></label>
183
+ <input style="margin-left:-14px; width:118px;" type="text" id="node-input-field" placeholder="payload">
184
+ <input style="margin-left:-14px; width:118px;" type="hidden" id="node-input-fieldType">
98
185
 
99
- <label style="margin-left: 25px;">
100
- <i class="fa fa-sign-out"></i>
101
- <span>Mode</span>
102
- </label>
103
- <select type="text" id="node-input-useSpawn" style="width:12%;margin-left:-30px;">
104
- <option value="false">exec mode- when the command is complete</option>
105
- <option value="true">spawn mode- while the command is running</option>
186
+ <label style="margin-left:14px;">
187
+ <i class="fa fa-random"></i>
188
+ <span>Parsing</span>
189
+ </label>
190
+ <select id="node-input-streamMode" style="width:90px; margin-left:-20px;">
191
+ <option value="delimited">Delimited</option>
192
+ <option value="raw">Raw</option>
106
193
  </select>
107
-
108
- <label style="margin-left: 25px;" for="node-input-field"><i class="fa fa-ellipsis-h"></i> <span data-i18n="node-red:common.label.property"></span></label>
109
- <input style="margin-left:-20px;width:130px;"type="text" id="node-input-field" placeholder="payload" >
110
- <input style="margin-left:-20px;width:130px;"type="hidden" id="node-input-fieldType">
111
-
112
- <span style="margin-left:20px;font-size:11px;">Split \n</span>
113
- <input type="checkbox" id="node-input-splitLine" style="display:inline-block; width:auto;margin-left:10px;">
194
+ <input type="text" id="node-input-delimiter" style="margin-left:4px; width:46px;" placeholder="\n">
195
+ </div>
196
+ <div id="node-es-raw-hint" style="display:none; text-align:right; margin-top:3px; font-size:0.75em; color:#aaa;">
197
+ Raw output may produce incomplete messages.
114
198
  </div>
115
-
116
199
  </script>
200
+
117
201
  <!--* javascript -->
118
202
  <script type="text/javascript">
119
- //** defining-variables
120
- RED.nodes.registerType('exec.service',{
121
- //color:"rgb(180, 100, 100)",
122
- color:"rgb(222, 204, 173)",
203
+ RED.nodes.registerType('exec.service', {
204
+ color: "#DEB887",
123
205
  category: 'function',
124
206
  defaults: {
125
- name: {value:""},
126
- currentLine: {row:0, column:0},
127
- command: {value:"node $file"},
128
- inputs: {value: 0},
129
- useSpawn: {value:"false"},
130
- field: {value:"payload", validate:RED.validators.typedInput("fieldType")},
131
- fieldType: {value:"msg"},
132
- format: {value:"javascript"},
133
- template: {value:`console.log(\`\n{\n\t"value": "exec.service",\n\t"purpose":"putting the ideas from the exec, template and queue nodes together"\n}\n\`)`},
134
- output: {value:"str"},
135
- inputEmpty: {value:true},
136
- queue: {value:1},
137
- cmdTemplate: {value:true},
138
- splitLine: {value:false},
139
- cleanQueue: {value:false}
207
+ name: { value: "" },
208
+ command: { value: "" },
209
+ template: { value: "" },
210
+ format: { value: "sh" },
211
+ currentLine: { value: { row: 0, column: 0 } },
212
+ restartDelay: { value: 3000 },
213
+ maxRetries: { value: 0 },
214
+ streamMode: { value: "delimited" },
215
+ delimiter: { value: "\\n", validate: function(v) { return typeof v === 'string' && v.length > 0; } },
216
+ output: { value: "str" },
217
+ field: { value: "payload", validate: RED.validators.typedInput("fieldType") },
218
+ fieldType: { value: "msg" }
140
219
  },
141
- inputs:0,
220
+ inputs: 0,
142
221
  outputs: 1,
143
- icon: "status.svg",
144
- //** function: label
222
+ icon: "cog.png",
145
223
  label: function() {
146
- return this.name||this._("exec.service");;
224
+ return this.name || "exec.service";
147
225
  },
148
- //** function: labelStyle
149
226
  labelStyle: function() {
150
- return this.name?"node_label_italic":"";
227
+ return this.name ? "node_label_italic" : "";
151
228
  },
152
- //** function: oneditprepare
153
229
  oneditprepare: function() {
154
230
  var that = this;
155
231
 
156
- if (!this.field) {
157
- this.field = 'payload';
158
- $("#node-input-field").val("payload");
159
- }
160
- if (!this.fieldType) {
161
- this.fieldType = 'msg';
162
- }
163
- $("#node-input-field").typedInput({
164
- default: 'msg',
165
- types: ['msg','flow','global'],
166
- typeField: $("#node-input-fieldType")
167
- });
232
+ if (!this.field) { this.field = 'payload'; }
233
+ if (!this.fieldType) { this.fieldType = 'msg'; }
168
234
 
169
235
  $("#node-input-field").typedInput({
170
236
  default: 'msg',
171
- types: ['msg','flow','global'],
237
+ types: ['msg', 'flow', 'global'],
172
238
  typeField: $("#node-input-fieldType")
173
239
  });
174
240
 
175
- //
176
- if (this.cmdTemplate === "true" || this.cmdTemplate === true) {
177
- $("#node-input-cmdTemplate").prop("checked",true);
178
- } else {
179
- $("#node-input-cmdTemplate").prop("checked",false);
241
+ function updateStreamUI() {
242
+ var mode = $("#node-input-streamMode").val();
243
+ var $sel = $("#node-input-streamMode");
244
+ var $rawOpt = $sel.find("option[value='raw']");
245
+ if (mode === 'raw') {
246
+ $("#node-input-delimiter").hide();
247
+ $("#node-es-raw-hint").show();
248
+ $sel.css("color", "#b8860b");
249
+ $rawOpt.text("⚠ Raw");
250
+ $sel.attr("title", "Raw mode emits unprocessed chunks — messages may be incomplete.");
251
+ } else {
252
+ $("#node-input-delimiter").show();
253
+ $("#node-es-raw-hint").hide();
254
+ $sel.css("color", "");
255
+ $rawOpt.text("Raw");
256
+ $sel.removeAttr("title");
257
+ }
180
258
  }
259
+ $("#node-input-streamMode").on("change", updateStreamUI);
260
+ updateStreamUI();
181
261
 
182
- this.editor = RED.editor.createEditor({
183
- id: 'node-input-template-editor',
184
- mode: 'ace/mode/html',
185
- value: $("#node-input-template").val()
262
+ // ── command suggestions ───────────────────────────────────────────
263
+ var commandSuggestions = [
264
+ "bash $file",
265
+ "node $file",
266
+ "python3 -u $file",
267
+ "inotifywait -m -e create --format '%f' ~/Downloads/",
268
+ "inotifywait -m -e create,modify,delete,move,close_write --format '{\"file\":\"%w%f\",\"event\":\"%e\"}' ~/Downloads/",
269
+ 'psql postgresql://user:pass@host:port/db -c "LISTEN my_channel;" -c "SELECT 1" --no-align --tuples-only',
270
+ "tail -F /var/log/syslog | grep ERROR",
271
+ "while true; do df -h | jc --df; sleep 5; done",
272
+ "while true; do top -b -n1 | jc --top 2>/dev/null; sleep 5; done"
273
+ ];
274
+ var $dl = $("#exec-service-command-suggestions");
275
+ $dl.empty();
276
+ commandSuggestions.forEach(function(cmd) {
277
+ $dl.append($("<option>").val(cmd));
186
278
  });
187
279
 
188
- RED.library.create({
189
- url:"templates", // where to get the data from
190
- type:"template", // the type of object the library is for
191
- editor:that.editor, // the field name the main text body goes to
192
- fields:['name','format','output'],
193
- ext: "txt"
280
+ // ── template editor ───────────────────────────────────────────────
281
+ var fmt = this.format || 'python';
282
+ this.editor = RED.editor.createEditor({
283
+ id: 'node-input-template-editor',
284
+ mode: 'ace/mode/' + fmt,
285
+ value: $("#node-input-template").val()
194
286
  });
195
- this.editor.focus();
196
287
 
288
+ $("#node-input-format").val(fmt);
197
289
  $("#node-input-format").on("change", function() {
198
- var mod = "ace/mode/"+$("#node-input-format").val();
199
- that.editor.getSession().setMode({
200
- path: mod,
201
- v: Date.now()
202
- });
290
+ var mode = "ace/mode/" + $(this).val();
291
+ that.editor.getSession().setMode({ path: mode, v: Date.now() });
203
292
  });
204
293
 
294
+ if (this.currentLine === undefined) { this.currentLine = { row: 0, column: 0 }; }
295
+ this.editor.resize(true);
296
+ this.editor.scrollToLine(this.currentLine.row + 1, true, true, function() {});
297
+ this.editor.gotoLine(this.currentLine.row + 1, this.currentLine.column + 1, true);
205
298
 
206
- //registerController = Vim.getRegisterController()
207
- //RegisterController.pushText(registerController)
208
- /* let firstTime_vimMode = true
209
- * $("#node-input-vimMode").on("change", function() {
210
- * try {
211
- * if ( that.vimMode === "true" || that.vimMode === true ){
212
- * if ( firstTime_vimMode ){
213
- * that.editor.setKeyboardHandler("ace/keyboard/vim")
214
- * } else {
215
- * that.vimMode = false
216
- * that.editor.setKeyboardHandler(null)
217
- * }
218
- * } else {
219
- * if ( firstTime_vimMode ){
220
- * that.editor.setKeyboardHandler(null)
221
- * } else {
222
- * that.vimMode = true
223
- * that.editor.setKeyboardHandler("ace/keyboard/vim")
224
- * }
225
- * }
226
- * } catch (e) {
227
- * console.log("Vim Mode only works with the Ace editor")
228
- * }
229
- setTimeout(function() {
230
- let panel = $(".ace_text-input");
231
- panel.focus();
232
- firstTime_vimMode = false
233
- }, 600);
234
- * });
235
- */
236
- let firstTime_inputEmpty = true
237
- $("#node-input-inputEmpty").on("change", function() {
238
- if ( that.inputEmpty === "true" || that.inputEmpty === true ){
239
- if ( firstTime_inputEmpty ){
240
- that.inputs = 0
241
- } else {
242
- that.inputEmpty = false
243
- that.inputs = 1
244
- }
245
- } else {
246
- if ( firstTime_inputEmpty ){
247
- that.inputs = 1
248
- } else {
249
- that.inputEmpty = true
250
- that.inputs = 0
251
- }
252
- }
253
- setTimeout(function() {
254
- firstTime_inputEmpty = false
255
- }, 600);
256
- });
299
+ RED.popover.tooltip(
300
+ $("#node-es-template-expand-editor"),
301
+ RED._("node-red:common.label.expand")
302
+ );
257
303
 
258
- if ( this.currentLine === undefined ){ this.currentLine = 1 }
259
- this.editor.resize(true);
260
- this.editor.scrollToLine(this.currentLine.row+1, true, true, function () {});
261
- this.editor.gotoLine(this.currentLine.row+1,this.currentLine.column+1,true);
262
- RED.popover.tooltip($("#node-template-expand-editor"), RED._("node-red:common.label.expand"));
263
- $("#node-template-expand-editor").on("click", function(e) {
304
+ $("#node-es-template-expand-editor").on("click", function(e) {
264
305
  e.preventDefault();
265
306
  var value = that.editor.getValue();
266
307
  RED.editor.editText({
267
- mode: $("#node-input-format").val(),
268
- value: value,
269
- width: "Infinity",
308
+ mode: $("#node-input-format").val(),
309
+ value: value,
310
+ width: "Infinity",
270
311
  cursor: that.editor.getCursorPosition(),
271
- complete: function(v,cursor) {
312
+ complete: function(v, cursor) {
272
313
  that.editor.setValue(v, -1);
273
- that.editor.gotoLine(cursor.row+1,cursor.column,false);
274
- setTimeout(function() {
275
- that.editor.focus();
276
- try {
277
- that.editor.setKeyboardHandler("ace/keyboard/vim")
278
- } catch (e) {
279
- console.log("Vim Mode only works with the Ace editor")
280
- }
281
- }, 300);
314
+ that.editor.gotoLine(cursor.row + 1, cursor.column, false);
315
+ setTimeout(function() { that.editor.focus(); }, 300);
316
+ }
317
+ });
318
+ });
319
+
320
+ // ── kill button ───────────────────────────────────────────────────
321
+ setTimeout(function() {
322
+ var nodeId = that.id;
323
+
324
+ function refreshKillBtn() {
325
+ $.get("exec-service/" + nodeId + "/status", function(data) {
326
+ var active = data.running || data.restarting;
327
+ $("#node-es-kill-btn").toggleClass("is-running", active);
328
+ });
329
+ }
330
+ refreshKillBtn();
331
+ var killBtnInterval = setInterval(refreshKillBtn, 1000);
332
+
333
+ $("#node-es-restart-btn").on("click", function(e) {
334
+ e.preventDefault();
335
+ e.stopPropagation();
336
+
337
+ function doRestart() {
338
+ $.post("exec-service/" + nodeId + "/restart");
339
+ setTimeout(refreshKillBtn, 300);
282
340
  }
283
- })
284
- })
341
+
342
+ $.get("exec-service/" + nodeId + "/status", function(data) {
343
+ console.log("[exec.service restart] click — nodeId:", nodeId, "running:", data.running, "restarting:", data.restarting);
344
+ if (data.running || data.restarting) {
345
+ var $dlg = $("<div>")
346
+ .html("Restart running service?")
347
+ .dialog({
348
+ modal: true,
349
+ title: "Restart service",
350
+ closeOnEscape: true,
351
+ width: 320,
352
+ buttons: {
353
+ "Cancel": function() { $(this).dialog("close"); },
354
+ "Restart": function() { $(this).dialog("close"); doRestart(); }
355
+ },
356
+ close: function() { $(this).dialog("destroy").remove(); }
357
+ });
358
+ $dlg.closest(".ui-dialog").css("z-index", 9999);
359
+ } else {
360
+ doRestart();
361
+ }
362
+ }).fail(function() { doRestart(); });
363
+ });
364
+
365
+ $("#node-es-kill-btn").on("click", function(e) {
366
+ e.preventDefault();
367
+ e.stopPropagation();
368
+
369
+ function doKill() {
370
+ $.post("exec-service/" + nodeId + "/kill");
371
+ setTimeout(refreshKillBtn, 300);
372
+ }
373
+
374
+ $.get("exec-service/" + nodeId + "/status", function(data) {
375
+ console.log("[exec.service kill] click — nodeId:", nodeId, "running:", data.running, "restarting:", data.restarting);
376
+ if (data.running || data.restarting) {
377
+ var msg = data.running ? "Kill running service?" : "Stop auto-restart?";
378
+ var $dlg = $("<div>")
379
+ .html(msg)
380
+ .dialog({
381
+ modal: true,
382
+ title: "Kill service",
383
+ closeOnEscape: true,
384
+ width: 320,
385
+ buttons: {
386
+ "Cancel": function() { $(this).dialog("close"); },
387
+ "Kill": function() { $(this).dialog("close"); doKill(); }
388
+ },
389
+ close: function() { $(this).dialog("destroy").remove(); }
390
+ });
391
+ $dlg.closest(".ui-dialog").css("z-index", 9999);
392
+ } else {
393
+ doKill();
394
+ }
395
+ }).fail(function() { doKill(); });
396
+ });
397
+
398
+ that._killBtnInterval = killBtnInterval;
399
+ }, 0);
285
400
  },
286
- //** function: oneditsave
287
401
  oneditsave: function() {
288
- this.cleanQueue = true
289
- this.currentLine = this.editor.getCursorPosition()
290
- $("#node-input-template").val(this.editor.getValue());
291
- this.editor.destroy();
292
- delete this.editor;
402
+ this.currentLine = this.editor.getCursorPosition();
403
+ $("#node-input-template").val(this.editor.getValue());
404
+ this.editor.destroy();
405
+ delete this.editor;
406
+ if (this._killBtnInterval) { clearInterval(this._killBtnInterval); delete this._killBtnInterval; }
293
407
  },
294
- //** function: oneditcancel
295
408
  oneditcancel: function() {
296
409
  this.editor.destroy();
297
410
  delete this.editor;
411
+ if (this._killBtnInterval) { clearInterval(this._killBtnInterval); delete this._killBtnInterval; }
298
412
  },
299
- //** function: oneditresize
300
- oneditresize: function(size) {
301
- var rows = $("#dialog-form>div:not(.node-text-editor-row)");
413
+ oneditresize: function() {
414
+ var rows = $("#dialog-form>div:not(.node-text-editor-row)");
302
415
  var height = $("#dialog-form").height();
303
- for (var i=0; i<rows.length; i++) {
416
+ for (var i = 0; i < rows.length; i++) {
304
417
  height -= $(rows[i]).outerHeight(true);
305
418
  }
306
419
  var editorRow = $("#dialog-form>div.node-text-editor-row");
307
- height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
308
- $(".node-text-editor").css("height",height+"px");
420
+ height -= (parseInt(editorRow.css("marginTop")) + parseInt(editorRow.css("marginBottom")));
421
+ $(".node-text-editor").css("height", height + "px");
309
422
  this.editor.resize();
310
423
  }
311
424
  });