linny-r 1.1.10 → 1.1.11

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/README.md CHANGED
@@ -166,7 +166,7 @@ Open the Command Line Interface (CLI) of your computer, change to your `WORKING_
166
166
  This response should be something similar to:
167
167
 
168
168
  <pre>
169
- Node.js server for Linny-R version 1.1.9
169
+ Node.js server for Linny-R version 1.1.11
170
170
  Node.js version: v18.11.0
171
171
  ... etc.
172
172
  </pre>
@@ -177,9 +177,9 @@ The Linny-R GUI should show in your browser window,
177
177
  while in the CLI you should see a long series of server log messages like:
178
178
 
179
179
  <pre>
180
- Static file: /index.html
181
- Static file: /scripts/iro.min.js
182
- Static file: /images/open.png
180
+ [2022-10-17 14:55:37] Static file: /index.html
181
+ [2022-10-17 14:55:37] Static file: /scripts/iro.min.js
182
+ [2022-10-17 14:55:37] Static file: /images/open.png
183
183
  ... etc.
184
184
  </pre>
185
185
 
@@ -300,7 +300,7 @@ you will see the command line options that allow you to run models in various wa
300
300
  ## Troubleshooting problems
301
301
 
302
302
  If during any of the steps above you encounter problems, please try to diagnose them and resolve them yourself.
303
- You can find a lot of useful information on the Linny-R documentation website:
303
+ You can find a lot of useful information on the Linny-R user documentation website:
304
304
  <a href="https://linny-r.info" target="_blank">https://linny-r.info</a>.
305
305
 
306
306
  To diagnose a problem, always look in the CLI box where Node.js is running,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "linny-r",
3
- "version": "1.1.10",
3
+ "version": "1.1.11",
4
4
  "description": "Executable graphical language with WYSIWYG editor for MILP models",
5
5
  "main": "server.js",
6
6
  "scripts": {
package/server.js CHANGED
@@ -162,12 +162,29 @@ if(SETTINGS.launch) {
162
162
  const cmd = (PLATFORM.startsWith('win') ? 'start' : 'open');
163
163
  child_process.exec(cmd + ' http://127.0.0.1:' + SETTINGS.port,
164
164
  (error, stdout, stderr) => {
165
- console.log('NOTICE: Failed to launch GUI in browser');
166
- console.log(stdout);
167
- console.log(stderr);
165
+ if(error) {
166
+ console.log('NOTICE: Failed to launch GUI in browser');
167
+ console.log(error);
168
+ console.log(stdout);
169
+ console.log(stderr);
170
+ }
168
171
  });
169
172
  }
170
173
 
174
+ // Server action logging functionality
175
+ // ===================================
176
+ // Only actions are logged to the console as with date and time;
177
+ // error messages are not prefixed, so these are logged directly.
178
+
179
+ function logAction(msg) {
180
+ // Log request processing to console with time-zone-aware date and time
181
+ const
182
+ t = new Date(),
183
+ tzt = new Date(t.getTime() - t.getTimezoneOffset()*60000),
184
+ dts = tzt.toISOString().substring(0, 19).replace('T', ' ');
185
+ console.log(`[${dts}] ${msg}`);
186
+ }
187
+
171
188
  // Version check functionality
172
189
  // ===========================
173
190
  // This section of code implements server responses to the request made
@@ -186,7 +203,21 @@ function autoCheck(res) {
186
203
  }
187
204
 
188
205
  // HTML page to show then the server is shut down by the user
189
- const SHUTDOWN_MESSAGE = `<!DOCTYPE html>
206
+ // NOTE: on a macOS machine, this is slightly more work
207
+ const
208
+ OS_TEXT = (PLATFORM === 'darwin' ? [
209
+ `<p>You can close the <em>Terminal</em> window that shows
210
+ <tt>[Process Terminated]</tt> at the bottom.
211
+ </p>`,
212
+ `open <em>Terminal</em> again, change to your Linny-R directory by typing:
213
+ </p>
214
+ <p><code>cd ${WORKING_DIRECTORY}</code></p>
215
+ <p>`
216
+ ] : [
217
+ '',
218
+ 'switch to your <em>Command Prompt</em> window '
219
+ ]),
220
+ SHUTDOWN_MESSAGE = `<!DOCTYPE html>
190
221
  <html lang="en-US">
191
222
  <head>
192
223
  <meta http-equiv="content-type" content="text/html; charset=UTF-8">
@@ -206,9 +237,8 @@ const SHUTDOWN_MESSAGE = `<!DOCTYPE html>
206
237
  </style>
207
238
  </head>
208
239
  <body>
209
- <h3>Linny-R server (127.0.0.1) is shutting down</h3>
210
- <p>To restart Linny-R, switch to your <em>${SETTINGS.cli_name}</em> window
211
- and there at the prompt` +
240
+ <h3>Linny-R server (127.0.0.1) is shutting down</h3>` + OS_TEXT[0] + `
241
+ <p>To restart Linny-R, ` + OS_TEXT[1] + ` and then at the prompt` +
212
242
  (VERSION_INFO.up_to_date ? '' : `
213
243
  first type:</p>
214
244
  <p><code>npm update linny-r</code><p>
@@ -250,7 +280,7 @@ function asFileName(s) {
250
280
  function autoSave(res, sp) {
251
281
  // Processes all auto-save & restore commands
252
282
  const action = sp.get('action').trim();
253
- console.log('Auto-save action:', action);
283
+ logAction('Auto-save action: ' + action);
254
284
  if(['purge', 'load', 'store'].indexOf(action) < 0) {
255
285
  // Invalid action => report error
256
286
  return servePlainText(res, `ERROR: Invalid auto-save action: "${action}"`);
@@ -378,7 +408,7 @@ function autoSaveStore(res, sp) {
378
408
  function repo(res, sp) {
379
409
  // Processes all repository commands
380
410
  const action = sp.get('action').trim();
381
- console.log('Repository action:', action);
411
+ logAction('Repository action: ' + action);
382
412
  if(action === 'id') return repoID(res);
383
413
  if(action === 'list') return repoList(res);
384
414
  if(action === 'add') return repoAdd(res, sp);
@@ -829,7 +859,7 @@ function anyOSpath(p) {
829
859
 
830
860
  function loadData(res, url) {
831
861
  // Passed parameter is the URL or full path
832
- console.log('Load data from', url);
862
+ logAction('Load data from ' + url);
833
863
  if(!url) servePlainText(res, 'ERROR: No URL or path');
834
864
  if(url.toLowerCase().startsWith('http')) {
835
865
  // URL => validate it, and then try to download its content as text
@@ -889,7 +919,7 @@ function receiver(res, sp) {
889
919
  }
890
920
  // Get the action from the search parameters
891
921
  const action = sp.get('action');
892
- console.log('Receiver action:', action, rpath, rfile);
922
+ logAction(`Receiver action: ${action} ${rpath} ${rfile}`);
893
923
  if(action === 'listen') {
894
924
  rcvrListen(res, rpath);
895
925
  } else if(action === 'abort') {
@@ -1042,7 +1072,7 @@ function rcvrCallBack(res, rpath, rfile, script) {
1042
1072
  }
1043
1073
  }
1044
1074
  if(cpath) {
1045
- console.log('Deleting', file_type, ' file:', cpath);
1075
+ logAction(`Deleting ${file_type} file: ${cpath}`);
1046
1076
  try {
1047
1077
  fs.unlinkSync(cpath);
1048
1078
  } catch(err) {
@@ -1058,7 +1088,7 @@ function rcvrCallBack(res, rpath, rfile, script) {
1058
1088
  }
1059
1089
  try {
1060
1090
  cmd = fs.readFileSync(path.join(WORKSPACE.callback, script), 'utf8');
1061
- console.log(`Executing callback command "${cmd}"`);
1091
+ logAction(`Executing callback command "${cmd}"`);
1062
1092
  child_process.exec(cmd, (error, stdout, stderr) => {
1063
1093
  console.log(stdout);
1064
1094
  if(error) {
@@ -1205,11 +1235,11 @@ function serveStaticFile(res, path) {
1205
1235
  if(path === '/' || path === '') path = '/index.html';
1206
1236
  if(path.startsWith('/diagrams/')) {
1207
1237
  // Serve diagrams from the (main)/user/diagrams/ sub-directory
1208
- console.log('Diagram:', path);
1238
+ logAction('Diagram: ' + path);
1209
1239
  path = '/user' + path;
1210
1240
  } else {
1211
1241
  // Other files from the (main)/static/ subdirectory
1212
- console.log('Static file:', path);
1242
+ logAction('Static file: ' + path);
1213
1243
  path = '/static' + path;
1214
1244
  }
1215
1245
  fs.readFile(MODULE_DIRECTORY + path, (err, data) => {
@@ -1237,7 +1267,7 @@ function convertSVGtoPNG(req, res, sp) {
1237
1267
  (new Date()).toISOString().slice(0, 19).replace(/[\-\:]/g, ''),
1238
1268
  fp = path.join(WORKSPACE.diagrams, fn);
1239
1269
  // NOTE: use binary encoding for SVG file
1240
- console.log('Saving SVG file:', fp);
1270
+ logAction('Saving SVG file: ' + fp);
1241
1271
  try {
1242
1272
  fs.writeFileSync(fp + '.svg', svg);
1243
1273
  } catch(error) {
@@ -1245,7 +1275,7 @@ function convertSVGtoPNG(req, res, sp) {
1245
1275
  }
1246
1276
  // Use Inkscape to convert SVG to the requested format
1247
1277
  if(SETTINGS.inkscape) {
1248
- console.log('Rendering image');
1278
+ logAction('Rendering image');
1249
1279
  let
1250
1280
  cmd = SETTINGS.inkscape,
1251
1281
  svg = fp + '.svg';
package/static/index.html CHANGED
@@ -176,6 +176,9 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
176
176
  // Invalid server response (should not occur, but just in case)
177
177
  UI.warn('Version check failed: "' + data + '"');
178
178
  }
179
+ // Schedule a new check 8 hours from now
180
+ setTimeout(checkForUpdates, 8*3600000);
181
+
179
182
  })
180
183
  .catch((error) => UI.warn(UI.WARNING.NO_CONNECTION, error));
181
184
  }
@@ -206,8 +209,8 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
206
209
  MONITOR = new GUIMonitor();
207
210
  RECEIVER = new GUIReceiver();
208
211
  // Check for software updates only when running on local server
209
- // NOTE: do this *after* GUI elements have been created, as the updater
210
- // uses a dialog
212
+ // NOTE: do this *after* GUI elements have been created, as the
213
+ // updater uses a dialog
211
214
  if(!SOLVER.user_id) checkForUpdates();
212
215
  // Initialize auto-saving function
213
216
  AUTO_SAVE = new ModelAutoSaver();
@@ -470,10 +473,10 @@ and move the cursor over the status bar">
470
473
  <input id="auto-save-hours" type="text" autocomplete="off">
471
474
  hours
472
475
  <img id="auto-save-clear-btn" class="btn enab" src="images/reset.png"
473
- title="Remove all auto-saved models from local storage">
476
+ title="Remove all auto-saved models from user workspace">
474
477
  </div>
475
478
  <div id="confirm-remove-models">
476
- Really remove all auto-saved from local storage?
479
+ Really remove all auto-saved models from user workspace?
477
480
  <strong>
478
481
  <img id="autosave-do-remove" class="inline-ok-btn"
479
482
  src="images/ok.png"> Yes
@@ -1064,7 +1067,7 @@ and move the cursor over the status bar">
1064
1067
  &mu; (mean: the average level &Sigma;/(&delta;+1) )
1065
1068
  </option>
1066
1069
  <option id="link-startup" value="5">
1067
- &#x2B9D; (start-up: 1 if X[t-1] = 0 &and; X[t] > 0, otherwise 0)
1070
+ &#x25B2; (start-up: 1 if X[t-1] = 0 &and; X[t] > 0, otherwise 0)
1068
1071
  </option>
1069
1072
  <option id="link-on" value="6">
1070
1073
  &plus; (positive: 1 if X[t] > 0, otherwise 0)
@@ -1073,7 +1076,7 @@ and move the cursor over the status bar">
1073
1076
  0 (zero: 1 if X[t] = 0, otherwise 0)
1074
1077
  </option>
1075
1078
  <option id="link-shutdown" value="10">
1076
- &#x2B9F; (shut-down: 1 if X[t-1] > 0 &and; X[t] = 0, otherwise 0)
1079
+ &#x25BC; (shut-down: 1 if X[t-1] > 0 &and; X[t] = 0, otherwise 0)
1077
1080
  </option>
1078
1081
  <option id="link-spinning" value="8">
1079
1082
  &#x2934; (spinning reserve: UB - X[t] if X[t] > 0, otherwise 0)
@@ -2422,12 +2425,12 @@ where X can be one or several of these letters: ABCDELPQ">
2422
2425
  <div class="docu-sym" id="docu-link">&#x2192;</div>
2423
2426
  <div class="docu-sym" id="docu-constraint">&#x2911;</div>
2424
2427
  <div class="docu-sym" id="docu-bi-constraint">&#x2194;</div>
2425
- <div class="docu-sym" id="docu-throughput">&#x2B86;</div>
2428
+ <div class="docu-sym" id="docu-throughput">&#x21C9;</div>
2426
2429
  <div class="docu-sym" id="docu-change">&#x0394;</div>
2427
2430
  <div class="docu-sym" id="docu-sum">&#x3A3;</div>
2428
2431
  <div class="docu-sym" id="docu-mean">&#x03BC;</div>
2429
- <div class="docu-sym" id="docu-startup">&#x2B9D;</div>
2430
- <div class="docu-sym" id="docu-shutdown">&#x2B9F;</div>
2432
+ <div class="docu-sym" id="docu-startup">&#x25B2;</div>
2433
+ <div class="docu-sym" id="docu-shutdown">&#x25BC;</div>
2431
2434
  <div class="docu-sym" id="docu-spinning-reserve">&#x2934;</div>
2432
2435
  <div class="docu-sym" id="docu-first-commit">&#x2732;</div>
2433
2436
  <div class="docu-sym" id="docu-infinity">&#x221E;</div>
@@ -37,6 +37,8 @@ body {
37
37
 
38
38
  body.waiting * { cursor: wait; }
39
39
 
40
+ @charset UTF-8;
41
+
40
42
  @font-face {
41
43
  font-family: Lato;
42
44
  src: url(fonts/Lato-Regular.ttf);
@@ -3804,6 +3806,11 @@ select.i-param {
3804
3806
  font-family: monospace;
3805
3807
  }
3806
3808
 
3809
+ #confirm-delete-from-repo-msg {
3810
+ height: calc(100% - 55px);
3811
+ }
3812
+
3813
+
3807
3814
  /* the FINDER DIALOG allows lookup of occurrences of entities */
3808
3815
  #finder-dlg {
3809
3816
  display: none;
@@ -4326,7 +4333,6 @@ div.call-stack-expr {
4326
4333
  #confirm-delete-from-repo-msg,
4327
4334
  #check-update-msg {
4328
4335
  width: calc(100% - 8px);
4329
- height: calc(100% - 55px);
4330
4336
  margin: 3px;
4331
4337
  overflow: auto;
4332
4338
  }
@@ -4733,7 +4733,9 @@ class GUIController extends Controller {
4733
4733
  // Update global variable (and force display) only for "real" messages
4734
4734
  this.time_last_message = t;
4735
4735
  dt = this.message_display_time;
4736
- SOUNDS[type].play();
4736
+ SOUNDS[type].play().catch(() => {
4737
+ console.log('NOTICE: Sounds will only play after first user action');
4738
+ });
4737
4739
  const
4738
4740
  now = [d.getHours(), d.getMinutes().toString().padStart(2, '0'),
4739
4741
  d.getSeconds().toString().padStart(2, '0')].join(':'),
@@ -1451,8 +1451,8 @@ class VirtualMachine {
1451
1451
  this.LM_PEAK_INC = 11; // Symbol: plus inside triangle ("peak-plus")
1452
1452
  // List of link multipliers that require binary ON/OFF variables
1453
1453
  this.LM_NEEDING_ON_OFF = [5, 6, 7, 8, 9, 10];
1454
- this.LM_SYMBOLS = ['', '\u21C9', '\u0394', '\u03A3', '\u03BC', '\u2B9D',
1455
- '+', '0', '\u2934', '\u2732', '\u2B9F', '\u2A39'];
1454
+ this.LM_SYMBOLS = ['', '\u21C9', '\u0394', '\u03A3', '\u03BC', '\u25B2',
1455
+ '+', '0', '\u2934', '\u2732', '\u25BC', '\u2A39'];
1456
1456
 
1457
1457
  // VM max. expression stack size
1458
1458
  this.MAX_STACK = 200;
@@ -1631,6 +1631,8 @@ class VirtualMachine {
1631
1631
  // Initialize error counters (error count will be reset to 0 for each block)
1632
1632
  this.error_count = 0;
1633
1633
  this.block_issues = 0;
1634
+ // NOTE: special tracking of potential solver license errors
1635
+ this.license_expired = 0;
1634
1636
  // Reset solver result arrays
1635
1637
  this.round_times.length = 0;
1636
1638
  this.solver_times.length = 0;
@@ -1708,7 +1710,8 @@ class VirtualMachine {
1708
1710
  // Other special values are very big POSITIVE numbers, so start
1709
1711
  // comparing `n` with the highest value
1710
1712
  if(n >= this.COMPUTING) return [true, '\u25A6']; // Checkered square
1711
- if(n >= this.NOT_COMPUTED) return [true, '\u2BBF']; // Circled X
1713
+ // NOTE: prettier circled bold X 2BBF does not display on macOS !!
1714
+ if(n >= this.NOT_COMPUTED) return [true, '\u2297']; // Circled X
1712
1715
  if(n >= this.UNDEFINED) return [true, '\u2047']; // Double question mark ??
1713
1716
  if(n >= this.PLUS_INFINITY) return [true, '\u221E'];
1714
1717
  return [false, n];
@@ -4526,7 +4529,7 @@ class VirtualMachine {
4526
4529
  checkLicense() {
4527
4530
  // Compares license expiry date (if set) with current time, and notifies
4528
4531
  // when three days or less remain
4529
- if(this.license_expires) {
4532
+ if(this.license_expires && this.license_expires.length) {
4530
4533
  // NOTE: expiry date has YYYY-MM-DD format
4531
4534
  const
4532
4535
  xds = this.license_expires[0].slice(-10).split('-'),
@@ -4582,6 +4585,9 @@ Solver status = ${json.status}`);
4582
4585
  }
4583
4586
  if(json.error) {
4584
4587
  const errmsg = 'Solver error: ' + json.error;
4588
+ if(errmsg.indexOf('license') >= 0 && errmsg.indexOf('expired') >= 0) {
4589
+ this.license_expired += 1;
4590
+ }
4585
4591
  this.logMessage(bnr, errmsg);
4586
4592
  UI.alert(errmsg);
4587
4593
  }
@@ -4633,6 +4639,10 @@ Solver status = ${json.status}`);
4633
4639
  if(this.block_issues) UI.warn('Issues occurred in ' +
4634
4640
  pluralS(this.block_issues, 'block') +
4635
4641
  ' -- check messages in monitor');
4642
+ if(this.license_expired > 0) {
4643
+ // Special message to draw attention to this critical error
4644
+ UI.alert('SOLVER LICENSE EXPIRED: Please check!');
4645
+ }
4636
4646
  // Call back to the console (if callback hook has been set)
4637
4647
  if(this.callback) this.callback(this);
4638
4648
  return;