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 +5 -5
- package/package.json +1 -1
- package/server.js +47 -17
- package/static/index.html +12 -9
- package/static/linny-r.css +7 -1
- package/static/scripts/linny-r-gui.js +3 -1
- package/static/scripts/linny-r-vm.js +14 -4
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.
|
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
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
|
-
|
166
|
-
|
167
|
-
|
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
|
-
|
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,
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
1238
|
+
logAction('Diagram: ' + path);
|
1209
1239
|
path = '/user' + path;
|
1210
1240
|
} else {
|
1211
1241
|
// Other files from the (main)/static/ subdirectory
|
1212
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
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
|
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
|
μ (mean: the average level Σ/(δ+1) )
|
1065
1068
|
</option>
|
1066
1069
|
<option id="link-startup" value="5">
|
1067
|
-
&#
|
1070
|
+
▲ (start-up: 1 if X[t-1] = 0 ∧ X[t] > 0, otherwise 0)
|
1068
1071
|
</option>
|
1069
1072
|
<option id="link-on" value="6">
|
1070
1073
|
+ (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
|
-
&#
|
1079
|
+
▼ (shut-down: 1 if X[t-1] > 0 ∧ X[t] = 0, otherwise 0)
|
1077
1080
|
</option>
|
1078
1081
|
<option id="link-spinning" value="8">
|
1079
1082
|
⤴ (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">→</div>
|
2423
2426
|
<div class="docu-sym" id="docu-constraint">⤑</div>
|
2424
2427
|
<div class="docu-sym" id="docu-bi-constraint">↔</div>
|
2425
|
-
<div class="docu-sym" id="docu-throughput">&#
|
2428
|
+
<div class="docu-sym" id="docu-throughput">⇉</div>
|
2426
2429
|
<div class="docu-sym" id="docu-change">Δ</div>
|
2427
2430
|
<div class="docu-sym" id="docu-sum">Σ</div>
|
2428
2431
|
<div class="docu-sym" id="docu-mean">μ</div>
|
2429
|
-
<div class="docu-sym" id="docu-startup">&#
|
2430
|
-
<div class="docu-sym" id="docu-shutdown">&#
|
2432
|
+
<div class="docu-sym" id="docu-startup">▲</div>
|
2433
|
+
<div class="docu-sym" id="docu-shutdown">▼</div>
|
2431
2434
|
<div class="docu-sym" id="docu-spinning-reserve">⤴</div>
|
2432
2435
|
<div class="docu-sym" id="docu-first-commit">✲</div>
|
2433
2436
|
<div class="docu-sym" id="docu-infinity">∞</div>
|
package/static/linny-r.css
CHANGED
@@ -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', '\
|
1455
|
-
'+', '0', '\u2934', '\u2732', '\
|
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
|
-
|
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;
|