Как управлять (запускать, прерывать) внешней программой через HTML-страницу с помощью ExpressJS? - PullRequest
0 голосов
/ 18 октября 2019

Я пытаюсь создать веб-мастер настройки, который запрашивает у пользователя некоторые значения, выводит их в файл конфигурации, а затем позволяет пользователю запускать серверную программу с использованием этой конфигурации.

Я выбрал ExpressJS, так как он казался относительно легким и легким в освоении. Тем не менее, у меня нет опыта работы с веб-программированием или JavaScript вообще.

Каким-то образом мне удалось построить страницы мастера, хотя мне кажется, что я использую что-то ужасно неправильное (приложение в основном не имеет состояния и перетаскивает пользовательские значения). через различные HTML-страницы путем рендеринга предыдущих значений в скрытые поля в форме новой страницы ...).

После всех страниц мастера появляется страница «управления», где пользователь может видеть (и редактировать)) вычисленная конфигурация. На странице есть две кнопки:

Одна запускает программу для проверки конфигурации и проверки на наличие ошибок. Это делается с помощью child_process.execSync(...) (так как это не занимает много времени) и работает довольно хорошо.

Другой должен запускать долгосрочную задачу в фоновом режиме. Во время выполнения задачи кнопки на странице должны быть отключены, за исключением кнопки «Прервать», которая должна убить фоновую задачу (еще не реализованную). После завершения задачи страницу необходимо перезагрузить с помощью включенных кнопок и поля состояния, в котором указано, была ли задача выполнена успешно. К сожалению, я не могу заставить это работать.

Я использую ExpressJS с pug.

control.js:

var express = require('express');
var router = express.Router();
var utils = require('./utils');
var createError = require('http-errors');
var child_process = require('child_process');

router.all('/', function(req, res,next) {
  var status = null;
  if ([req.app.locals.buttons.doStuff].includes(req.body.submit)) {
    next();
  }
  switch(req.body.submit) {
    case req.app.locals.buttons.validate:
      try {
        utils.validate(req.body.config_text); // helper function that basically calls an external program via child_process.execSync
        status = "config.yaml is valid!"
      } catch (err) {
        status = "config.yaml is invalid: " + err
      }
      break;
    case req.app.locals.buttons.doStuff:
      res.render('control', { title: req.app.locals.title, buttons: req.app.locals.buttons, config_data: req.body.config_text, status: status, disabled: true });
      return;
  }
  res.render('control', { title: req.app.locals.title, buttons: req.app.locals.buttons, config_data: req.body.config_text, status: status });
});

router.post('/', function(req, res, next) {
  var status = null;
  console.log("Entering command mode: " + req.body.submit);
  switch(req.body.submit) {
    case req.app.locals.buttons.doStuff:
      status = "Doing stuff ... this will take a while (timeout after 1h)."
      console.log(status);
      child_process.exec("doStuff arg1 arg2 arg3", {cwd: req.app.locals.dostuff_directory, timeout: 3600000}, function(err, stdout, stderr) {
        status = "doStuff successfully finished!"
        if (err) {
          status = "Error while doing stuff: \n" + stderr;
        }
        res.render('control', { title: req.app.locals.title, buttons: req.app.locals.buttons, config_data: req.body.config_text, status: status });
      });
      break;
    default:
      status = "Unknown command: " + req.body.submit;
      res.render('control', { title: req.app.locals.title, buttons: req.app.locals.buttons, config_data: req.body.config_text, status: status });
      break;
  }
});

module.exports = router;

и соответствующий control.pug:

extends layout // basically just places the content block into <body>

block content
  h1= title
  h3 Control Page

  form(method="post", autocomplete="off")
    label(for="config_text") config.yaml
    textarea(name="config_text", cols="70", rows="35")
      = config_data
    pre
      code(name="status")
        = status
    div(class="controls")
      input(type="submit", name="submit", value=buttons.validate, disabled=disabled)
      input(type="submit", name="submit", value=buttons.doStuff, disabled=disabled)
      if (disabled)
        input(type="submit", name="submit", value=buttons.abort)

Если я нажму кнопку 'doStuff', кнопки будут отключены, но задача не запустится, и я получу error: Cannot set headers after they are sent to the client.

Я думаю, я мог бы использовать child_process.execSync как обходной путь, так как это, кажется, работает, но я предполагаю, что это остановит страницу во время выполнения задачи. У меня также есть ощущение, что я должен использовать другой метод реагирования на нажатия кнопок, чем теги HTML form.

Как мне добиться желаемого поведения?

...