linny-r 1.1.6 → 1.1.8
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 +19 -18
- package/console.js +72 -11
- package/package.json +2 -2
- package/post-install.js +8 -5
- package/server.js +74 -170
- package/static/index.html +21 -17
package/README.md
CHANGED
@@ -5,7 +5,8 @@
|
|
5
5
|
<a href="https://en.wikipedia.org/wiki/Unit_commitment_problem_in_electrical_power_production" target="_blank">unit commitment problems</a> (UCP) and
|
6
6
|
<a href="https://en.wikipedia.org/wiki/Generation_expansion_planning" target="_blank">generation expansion planning</a> (GEP).</p>
|
7
7
|
|
8
|
-
The graphical language and WYSIWYG model editor are developed by **Pieter Bots** at
|
8
|
+
The graphical language and WYSIWYG model editor are developed by **Pieter Bots** at
|
9
|
+
<a href="https://tudelft.nl" target="_blank">Delft University of Technology</a>.
|
9
10
|
|
10
11
|
Originally implemented in Delphi Pascal, Linny-R is now developed in HTML+CSS+JavaScript
|
11
12
|
so as to be platform-independent and 100% transparent open source (under the MIT license).
|
@@ -13,21 +14,21 @@ The software comprises a server that runs on Node.js, and a graphical user inter
|
|
13
14
|
|
14
15
|
You can play with the most recent release of Linny-R on
|
15
16
|
<a href="https://sysmod.tbm.tudelft.nl/linny-r" target="_blank">this server hosted at TU Delft</a>.
|
16
|
-
Note that this server imposes restrictions on solver time and the total number of blocks it will solve per run.
|
17
|
+
Note that this server imposes restrictions on solver time and the total number of blocks it will solve per run.
|
17
18
|
If you install Linny-R on your own machine, no such restrictions apply.
|
18
19
|
|
19
20
|
Documentation for Linny-R is still scant, but it is growing. You can contribute yourself (in "wiki fashion")
|
20
|
-
via the official documentation site https://linny-r.info.
|
21
|
+
via the official documentation site <a href="https://linny-r.info" target="_blank">https://linny-r.info</a>.
|
21
22
|
|
22
23
|
### Installing Node.js
|
23
24
|
|
24
25
|
Linny-R is developed as a JavaScript package, and requires that **Node.js** is installed on your computer.
|
25
|
-
|
26
|
+
This software can be downloaded from <a href="https://nodejs.org" target="_blank">https://nodejs.org</a>.
|
26
27
|
Make sure that you choose the correct installer for your computer.
|
27
|
-
Linny-R is developed using the
|
28
|
+
Linny-R is developed using the _current_ release. Presently (October 2022) this is 18.10.0.
|
28
29
|
|
29
30
|
Run the installer and accept the default settings.
|
30
|
-
There is **no** need to install the optional
|
31
|
+
There is **no** need to install the optional _Tools for Native Modules_.
|
31
32
|
|
32
33
|
Open the Command Line Interface (CLI) of your computer.
|
33
34
|
On macOS, this will be `Terminal`, on Windows `Command Prompt`.
|
@@ -101,13 +102,13 @@ and the sub-directory `static`. This `static` directory should contain three HTM
|
|
101
102
|
|
102
103
|
It should also contain the style sheet `linny-r.css` required by the GUI.
|
103
104
|
|
104
|
-
The
|
105
|
+
The sub-directories of `static` contain files that are served to the browser by the script
|
105
106
|
`server.js` when it is running in Node.js.
|
106
107
|
|
107
108
|
### Configuring the MILP solver
|
108
109
|
|
109
110
|
Linny-R presently supports two MILP solvers: Gurobi and LP_solve.
|
110
|
-
Gurobi is
|
111
|
+
Gurobi is _considerably_ more powerful than the open source LP_solve solver that has powered Linny-R since 2009,
|
111
112
|
but it requires a license.
|
112
113
|
Academic licenses can be obtained by students and staff of eligible institutions.
|
113
114
|
|
@@ -167,7 +168,7 @@ Open the Command Line Interface (CLI) of your computer, change to your `WORKING_
|
|
167
168
|
This response should be something similar to:
|
168
169
|
|
169
170
|
<pre>
|
170
|
-
Node.js server for Linny-R version 1.
|
171
|
+
Node.js server for Linny-R version 1.1.8
|
171
172
|
Node.js version: v18.10.0
|
172
173
|
... etc.
|
173
174
|
</pre>
|
@@ -191,7 +192,7 @@ stating the name of the solver.
|
|
191
192
|
You can then test the GUI by creating a simple model.
|
192
193
|
Make one that has at least one process that outputs a product,
|
193
194
|
and this product must have a price or a set lower bound, otherwise the model will have no objective function.
|
194
|
-
Then click on the
|
195
|
+
Then click on the _Solve_ button at the bottom of the left-hand tool bar.
|
195
196
|
The Linny-R icon in the upper left corner should start rotating, while the status bar at the bottom should display:
|
196
197
|
|
197
198
|
<pre>
|
@@ -211,10 +212,10 @@ Solve block 1 a
|
|
211
212
|
The user workspace is created when the server is run for the first time.
|
212
213
|
The sub-directories of this directory `user` are used by Linny-R to store files.
|
213
214
|
|
214
|
-
* `channel` and `callback` will be used to interact with Linny-R via its
|
215
|
+
* `channel` and `callback` will be used to interact with Linny-R via its _Receiver_
|
215
216
|
* `diagrams` will be used to render Scalable Vector Graphics (SVG) files as
|
216
217
|
Portable Network Graphics (PNG) using Inkscape (if installed)
|
217
|
-
* `modules` will contain models stored in the `local host`
|
218
|
+
* `modules` will contain models stored in the `local host` _repository_
|
218
219
|
* `solver` will contain the files that are exchanged with the Mixed Integer Linear Programming (MILP) solver
|
219
220
|
(the names of the files that will appear in this directory may vary, depending on the MILP-solver you use)
|
220
221
|
|
@@ -239,7 +240,7 @@ When you download a diagram, it will be saved as a .svg file.
|
|
239
240
|
These files can be viewed and edited using Inkscape, an open source vector graphics editor.
|
240
241
|
|
241
242
|
As it may be tedious to first save a diagram as SVG and then render it manually as a bitmap image,
|
242
|
-
Linny-R features a *Render diagram as bitmap* button on the top toolbar, and on the bottom toolbar of the
|
243
|
+
Linny-R features a *Render diagram as bitmap* button on the top toolbar, and on the bottom toolbar of the _Chart manager_.
|
243
244
|
When you click it, Linny-R will send the image as SVG to the server.
|
244
245
|
The server script will save the SVG in the `user/diagrams` sub-directory,
|
245
246
|
and then try to execute an Inkscape command that will convert this SVG to a PNG image file in the same directory.
|
@@ -261,8 +262,8 @@ so you need to do this yourself.
|
|
261
262
|
To facilitate start-up, you can create a shortcut icon on your desktop.
|
262
263
|
|
263
264
|
On a Windows machine, change to your Linny-R folder, right-click on the batch file `linny-r.bat`,
|
264
|
-
and select the
|
265
|
-
Then right-click on the shortcut file to edit its properties, and click the
|
265
|
+
and select the _Create shortcut_ option.
|
266
|
+
Then right-click on the shortcut file to edit its properties, and click the _Change Icon_ button.
|
266
267
|
The dialog that then appears will allow you to go to the sub-folder `node_modules\linny-r\static\images`,
|
267
268
|
where you should select the file `linny-r.ico`.
|
268
269
|
Finally, rename the shortcut to `Linny-R` and move or copy it to your desktop.
|
@@ -281,8 +282,7 @@ and from there drag/drop the file `linny-r.icns` on the icon shown in the top le
|
|
281
282
|
|
282
283
|
If you have not configured a "click-start" icon as described above,
|
283
284
|
you must start a modeling session with Linny-R by opening a CLI box,
|
284
|
-
then change to the Linny-R directory
|
285
|
-
open your browser, and then navigate to http://127.0.0.1:5050
|
285
|
+
then change to the Linny-R directory and type `linny-r`.
|
286
286
|
|
287
287
|
To shut down the server, click on the local host icon in the upper right corner of the Linny-R GUI in your browser.
|
288
288
|
Alternatively, you can stop the server by repeatedly pressing ``Ctrl+C`` in the CLI box.
|
@@ -306,7 +306,8 @@ you will see the command line options that allow you to run models in various wa
|
|
306
306
|
### Troubleshooting problems
|
307
307
|
|
308
308
|
If during any of the steps above you encounter problems, please try to diagnose them and resolve them yourself.
|
309
|
-
You can find a lot of useful information on the Linny-R documentatio website:
|
309
|
+
You can find a lot of useful information on the Linny-R documentatio website:
|
310
|
+
<a href="https://linny-r.info" target="_blank">https://linny-r.info</a>.
|
310
311
|
|
311
312
|
To diagnose a problem, always look in the CLI box where Node.js is running,
|
312
313
|
as informative server-side error messages will appear there.
|
package/console.js
CHANGED
@@ -42,22 +42,48 @@ SOFTWARE.
|
|
42
42
|
global.NODE = true;
|
43
43
|
|
44
44
|
const
|
45
|
-
VERSION_NUMBER = '1.1.2',
|
46
45
|
WORKING_DIRECTORY = process.cwd(),
|
47
46
|
path = require('path'),
|
48
|
-
|
47
|
+
MODULE_DIRECTORY = path.join(WORKING_DIRECTORY, 'node_modules', 'linny-r'),
|
49
48
|
// Load the required Node.js modules
|
49
|
+
child_process = require('child_process'),
|
50
50
|
fs = require('fs'),
|
51
51
|
os = require('os'),
|
52
52
|
readline = require('readline'),
|
53
53
|
// Get the platform name (win32, macOS, linux) of the user's computer
|
54
|
-
PLATFORM = os.platform()
|
54
|
+
PLATFORM = os.platform(),
|
55
|
+
// Get version of the installed Linny-R package
|
56
|
+
VERSION_INFO = getVersionInfo();
|
57
|
+
|
58
|
+
function getVersionInfo() {
|
59
|
+
// Reads version info from `package.json`
|
60
|
+
const info = {
|
61
|
+
current: 0,
|
62
|
+
current_time: 0,
|
63
|
+
latest: '0',
|
64
|
+
latest_time: 0,
|
65
|
+
up_to_date: false
|
66
|
+
};
|
67
|
+
try {
|
68
|
+
info.current = require('./package.json').version;
|
69
|
+
} catch(err) {
|
70
|
+
console.log('ERROR: Failed to read package.json');
|
71
|
+
console.log(err);
|
72
|
+
console.log('This indicates that Linny-R is not installed properly.');
|
73
|
+
process.exit();
|
74
|
+
}
|
75
|
+
// NOTE: unlike the Linny-R server, the console does not routinely
|
76
|
+
// check whether version is up-to-date is optional because this is
|
77
|
+
// a time-consuming action that would reduce multi-run performance.
|
78
|
+
// See command line options (much further down)
|
79
|
+
console.log('\nLinny-R Console version', info.current);
|
80
|
+
return info;
|
81
|
+
}
|
55
82
|
|
56
|
-
//
|
57
|
-
console.log('\nNode.js Linny-R console version', VERSION_NUMBER);
|
83
|
+
// Output some configuration information to the console
|
58
84
|
console.log('Node.js version:', process.version);
|
59
85
|
console.log('Platform:', PLATFORM, '(' + os.type() + ')');
|
60
|
-
console.log('
|
86
|
+
console.log('Module directory:', MODULE_DIRECTORY);
|
61
87
|
console.log('Working directory:', WORKING_DIRECTORY);
|
62
88
|
|
63
89
|
const
|
@@ -86,6 +112,7 @@ Usage: node console [options]
|
|
86
112
|
Possible options are:
|
87
113
|
channel=[identifier] will start listening at the specified channel
|
88
114
|
(FUTURE OPTION)
|
115
|
+
check will report whether current version is up-to-date
|
89
116
|
model=[path] will load model file in [path]
|
90
117
|
module=[name@repo] will load model [name] from repository [repo]
|
91
118
|
(if @repo is blank, repository "local host" is used)
|
@@ -125,9 +152,8 @@ function checkNodeModule(name) {
|
|
125
152
|
// XML-related functions defined in `linny-r-utils.js`
|
126
153
|
global.XML_PARSER = new DOMParser();
|
127
154
|
|
128
|
-
//
|
129
|
-
|
130
|
-
global.LINNY_R_VERSION = '1.1.0';
|
155
|
+
// Set the current version number
|
156
|
+
global.LINNY_R_VERSION = VERSION_INFO.current;
|
131
157
|
|
132
158
|
///////////////////////////////////////////////////////////////////////////////
|
133
159
|
// Class definitions must precede instatiation of Linny-R components //
|
@@ -675,17 +701,21 @@ function commandLineSettings() {
|
|
675
701
|
// Sets default settings, and then checks the command line arguments
|
676
702
|
const settings = {
|
677
703
|
cli_name: (PLATFORM.startsWith('win') ? 'Command Prompt' : 'Terminal'),
|
678
|
-
|
704
|
+
check: false,
|
679
705
|
preferred_solver: '',
|
706
|
+
run: false,
|
680
707
|
solver: '',
|
681
708
|
solver_path: '',
|
682
|
-
user_dir: path.join(WORKING_DIRECTORY, 'user')
|
709
|
+
user_dir: path.join(WORKING_DIRECTORY, 'user'),
|
710
|
+
verbose: false
|
683
711
|
};
|
684
712
|
let show_usage = process.argv.length < 3;
|
685
713
|
for(let i = 2; i < process.argv.length; i++) {
|
686
714
|
const lca = process.argv[i].toLowerCase();
|
687
715
|
if(lca === 'help' || lca === '?' || lca.startsWith('-')) {
|
688
716
|
show_usage = true;
|
717
|
+
} else if(lca === 'check') {
|
718
|
+
settings.check = true;
|
689
719
|
} else if(lca === 'run') {
|
690
720
|
settings.run = true;
|
691
721
|
} else if(lca === 'verbose') {
|
@@ -767,6 +797,8 @@ function commandLineSettings() {
|
|
767
797
|
console.log(usage);
|
768
798
|
process.exit();
|
769
799
|
}
|
800
|
+
// Perform version check only if asked for
|
801
|
+
if(settings.check) checkForUpdates();
|
770
802
|
// Check whether MILP solver(s) and Inkscape have been installed
|
771
803
|
const path_list = process.env.PATH.split(path.delimiter);
|
772
804
|
let gurobi_path = '',
|
@@ -876,6 +908,35 @@ function createWorkspace() {
|
|
876
908
|
return ws;
|
877
909
|
}
|
878
910
|
|
911
|
+
function checkForUpdates() {
|
912
|
+
// Check for newer version of the Node.js package `linny-r`
|
913
|
+
// NOTE: use `info` as shorthand for the global constant
|
914
|
+
const info = VERSION_INFO;
|
915
|
+
try {
|
916
|
+
const
|
917
|
+
json = child_process.execSync('npm show linny-r time version --json'),
|
918
|
+
obj = JSON.parse(json);
|
919
|
+
info.latest = obj.version;
|
920
|
+
info.latest_time = new Date(Date.parse(obj.time[info.latest]));
|
921
|
+
info.current_time = new Date(Date.parse(obj.time[info.current]));
|
922
|
+
info.up_to_date = info.current === info.latest;
|
923
|
+
} catch(err) {
|
924
|
+
// `latest` = 0 indicates that version check failed
|
925
|
+
info.latest = 0;
|
926
|
+
}
|
927
|
+
if(!info.latest) {
|
928
|
+
console.log('WARNING: Could not check for updates');
|
929
|
+
} else if(!info.up_to_date) {
|
930
|
+
console.log('UPDATE: Version ' + info.latest + ' was released on ' +
|
931
|
+
info.latest_time.toString());
|
932
|
+
} else {
|
933
|
+
console.log('Version ' + info.latest + ' is up-to-date (released on ' +
|
934
|
+
info.latest_time.toString() + ')');
|
935
|
+
}
|
936
|
+
// Return TRUE if current version is the latest one
|
937
|
+
return info.up_to_date;
|
938
|
+
}
|
939
|
+
|
879
940
|
// Initialize the solver
|
880
941
|
const SOLVER = new MILPSolver(SETTINGS, WORKSPACE);
|
881
942
|
/*
|
package/package.json
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
{
|
2
2
|
"name": "linny-r",
|
3
|
-
"version": "1.1.
|
3
|
+
"version": "1.1.8",
|
4
4
|
"description": "Executable graphical language with WYSIWYG editor for MILP models",
|
5
5
|
"main": "server.js",
|
6
6
|
"scripts": {
|
7
|
-
"postinstall": "post-install.js",
|
7
|
+
"postinstall": "node post-install.js",
|
8
8
|
"test": "echo \"Error: no test specified\" && exit 1"
|
9
9
|
},
|
10
10
|
"dependencies": {
|
package/post-install.js
CHANGED
@@ -52,21 +52,24 @@ const
|
|
52
52
|
path = require('path'),
|
53
53
|
os = require('os'),
|
54
54
|
PLATFORM = os.platform(),
|
55
|
-
|
55
|
+
mod_dir = path.join('node_modules', 'linny-r'),
|
56
|
+
// NOTE: working directory for this script is the *module* directory,
|
57
|
+
// which is two levels deeper than the actual working directory
|
58
|
+
WORKING_DIRECTORY = process.cwd().replace(path.sep + mod_dir, ''),
|
56
59
|
lines = [
|
57
60
|
'# The first line (without the comment symbol #) should be like this:',
|
58
61
|
'# cd ',
|
59
62
|
'',
|
60
63
|
'# Then this command to launch the Linny-R server should work:',
|
61
|
-
|
64
|
+
`node ${mod_dir}${path.sep}server launch`
|
62
65
|
];
|
63
66
|
let sp;
|
64
67
|
if(PLATFORM.startsWith('win')) {
|
65
68
|
sp = path.join(WORKING_DIRECTORY, 'linny-r.bat');
|
66
|
-
lines[1] += 'C:\\path\\to\\
|
69
|
+
lines[1] += 'C:\\path\\to\\\\your\\Linny-R\\directory';
|
67
70
|
} else {
|
68
71
|
sp = path.join(WORKING_DIRECTORY, 'linny-r.command');
|
69
|
-
lines[1] += '/path/to/
|
72
|
+
lines[1] += '/path/to/your/Linny-R/directory';
|
70
73
|
}
|
71
74
|
lines[2] = 'cd ' + WORKING_DIRECTORY;
|
72
75
|
try {
|
@@ -76,7 +79,7 @@ try {
|
|
76
79
|
// Only write the script content if the file it does not yet exist
|
77
80
|
console.log('Creating launch script:', sp);
|
78
81
|
let code = lines.join(os.EOL);
|
79
|
-
if(PLATFORM.startsWith('win')) code = code.
|
82
|
+
if(PLATFORM.startsWith('win')) code = code.replaceAll('#', '::');
|
80
83
|
fs.writeFileSync(sp, code, 'utf8');
|
81
84
|
}
|
82
85
|
} catch(err) {
|
package/server.js
CHANGED
@@ -38,17 +38,11 @@ SOFTWARE.
|
|
38
38
|
///////////////////////////////////////////////////////////////////////////////
|
39
39
|
|
40
40
|
const
|
41
|
-
// The version number of this Linny-R server in Node.js
|
42
|
-
VERSION_NUMBER = '1.1.2',
|
43
|
-
|
44
|
-
// The URL of the official Linny-R website (with the most recent release)
|
45
|
-
PUBLIC_LINNY_R_URL = 'https://sysmod.tbm.tudelft.nl/linny-r',
|
46
|
-
|
47
41
|
// The current working directory (from where Node.js was started) is
|
48
42
|
// assumed to be the main directory
|
49
43
|
path = require('path'),
|
50
44
|
WORKING_DIRECTORY = process.cwd(),
|
51
|
-
|
45
|
+
MODULE_DIRECTORY = path.join(WORKING_DIRECTORY, 'node_modules', 'linny-r'),
|
52
46
|
|
53
47
|
// Get the required built-in Node.js modules
|
54
48
|
child_process = require('child_process'),
|
@@ -59,16 +53,57 @@ const
|
|
59
53
|
os = require('os'),
|
60
54
|
|
61
55
|
// Get the platform name (win32, macOS, linux) of the user's computer
|
62
|
-
PLATFORM = os.platform()
|
56
|
+
PLATFORM = os.platform(),
|
57
|
+
|
58
|
+
// Get version of the installed Linny-R package
|
59
|
+
VERSION_INFO = getVersionInfo();
|
60
|
+
|
61
|
+
function getVersionInfo() {
|
62
|
+
// Reads version info from `package.json`
|
63
|
+
const info = {
|
64
|
+
current: 0,
|
65
|
+
current_time: 0,
|
66
|
+
latest: '0',
|
67
|
+
latest_time: 0,
|
68
|
+
up_to_date: false
|
69
|
+
};
|
70
|
+
try {
|
71
|
+
info.current = require('./package.json').version;
|
72
|
+
} catch(err) {
|
73
|
+
console.log('ERROR: Failed to read package.json');
|
74
|
+
console.log(err);
|
75
|
+
console.log('This indicates that Linny-R is not installed properly.');
|
76
|
+
process.exit();
|
77
|
+
}
|
78
|
+
try {
|
79
|
+
const
|
80
|
+
json = child_process.execSync('npm show linny-r time version --json'),
|
81
|
+
obj = JSON.parse(json);
|
82
|
+
info.latest = obj.version;
|
83
|
+
info.latest_time = new Date(Date.parse(obj.time[info.latest]));
|
84
|
+
info.current_time = new Date(Date.parse(obj.time[info.current]));
|
85
|
+
info.up_to_date = info.current === info.latest;
|
86
|
+
} catch(err) {
|
87
|
+
// `latest` = 0 indicates that version check failed
|
88
|
+
info.latest = 0;
|
89
|
+
}
|
90
|
+
console.log('\nNode.js server for Linny-R version', info.current);
|
91
|
+
if(!info.latest) {
|
92
|
+
console.log('WARNING: Could not check for updates');
|
93
|
+
} else if(!info.up_to_date) {
|
94
|
+
console.log('UPDATE: Version ' + info.latest + ' was released on ' +
|
95
|
+
info.latest_time.toString());
|
96
|
+
}
|
97
|
+
return info;
|
98
|
+
}
|
63
99
|
|
64
|
-
//
|
65
|
-
console.log('\nNode.js server for Linny-R version', VERSION_NUMBER);
|
100
|
+
// Output some configuration information to the console
|
66
101
|
console.log('Node.js version:', process.version);
|
67
102
|
console.log('Platform:', PLATFORM, '(' + os.type() + ')');
|
68
|
-
console.log('
|
103
|
+
console.log('Module directory:', MODULE_DIRECTORY);
|
69
104
|
console.log('Working directory:', WORKING_DIRECTORY);
|
70
105
|
|
71
|
-
// Only
|
106
|
+
// Only now require the Node.js modules that are not "built-in"
|
72
107
|
|
73
108
|
const
|
74
109
|
{ DOMParser } = checkNodeModule('@xmldom/xmldom');
|
@@ -83,12 +118,10 @@ function checkNodeModule(name) {
|
|
83
118
|
}
|
84
119
|
}
|
85
120
|
|
121
|
+
|
86
122
|
// Load class MILPSolver
|
87
123
|
const MILPSolver = require('./static/scripts/linny-r-milp.js');
|
88
124
|
|
89
|
-
///////////////////////////////////////////////////////////////////////////////
|
90
|
-
// Code executed at start-up continues here //
|
91
|
-
///////////////////////////////////////////////////////////////////////////////
|
92
125
|
|
93
126
|
// Default settings are used unless these are overruled by arguments on the
|
94
127
|
// command line. Possible arguments are:
|
@@ -135,54 +168,25 @@ if(SETTINGS.launch) {
|
|
135
168
|
});
|
136
169
|
}
|
137
170
|
|
138
|
-
// Version check
|
139
|
-
//
|
140
|
-
// This section of code implements server responses to
|
141
|
-
// browser immediately after loading the GUI page (`index.html`)
|
142
|
-
// user clicks on the link "Version ..." below the Linny-R logo in the upper
|
143
|
-
// left corner of the GUI page.
|
144
|
-
|
145
|
-
const
|
146
|
-
|
147
|
-
VERSION_MESSAGE = `<!DOCTYPE html>
|
148
|
-
<html lang="en-US">
|
149
|
-
<head>
|
150
|
-
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
|
151
|
-
<title>Linny-R version information</title>
|
152
|
-
<link rel="shortcut icon" type="image/png" href="../images/icon.png">
|
153
|
-
<style>
|
154
|
-
body {
|
155
|
-
font-family: sans-serif;
|
156
|
-
font-size: 16px;
|
157
|
-
}
|
158
|
-
#linny-r-logo {
|
159
|
-
height: 40px;
|
160
|
-
margin-bottom: -10px;
|
161
|
-
}
|
162
|
-
</style>
|
163
|
-
</head>
|
164
|
-
<body>
|
165
|
-
<img id="linny-r-logo" src="../images/logo.png">
|
166
|
-
%1%
|
167
|
-
</body>
|
168
|
-
</html>`,
|
169
|
-
|
170
|
-
NO_INTERNET_MESSAGE = `
|
171
|
-
<h3>Version check failed</h3>
|
172
|
-
<p>
|
173
|
-
No contact with the on-line Linny-R server --
|
174
|
-
please check your internet connection.
|
175
|
-
</p>`,
|
176
|
-
|
177
|
-
UP_TO_DATE_MESSAGE = `
|
178
|
-
<h3>Version JS-%1% is up-to-date</h3>
|
179
|
-
<p>Released on %2%</p>`,
|
171
|
+
// Version check functionality
|
172
|
+
// ===========================
|
173
|
+
// This section of code implements server responses to the request made
|
174
|
+
// by the browser immediately after loading the GUI page (`index.html`)
|
180
175
|
|
181
|
-
|
182
|
-
|
183
|
-
|
176
|
+
function autoCheck(res) {
|
177
|
+
// Serves a string with the current version number plus info on a
|
178
|
+
// newer release if this is available
|
179
|
+
let check = VERSION_INFO.current + '|';
|
180
|
+
if(VERSION_INFO.up_to_date) {
|
181
|
+
check += 'up-to-date';
|
182
|
+
} else {
|
183
|
+
check += VERSION_INFO.latest + '|' + VERSION_INFO.latest_time;
|
184
|
+
}
|
185
|
+
servePlainText(res, check);
|
186
|
+
}
|
184
187
|
|
185
|
-
|
188
|
+
// HTML page to show then the server is shut down by the user
|
189
|
+
const SHUTDOWN_MESSAGE = `<!DOCTYPE html>
|
186
190
|
<html lang="en-US">
|
187
191
|
<head>
|
188
192
|
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
|
@@ -197,12 +201,14 @@ SHUTDOWN_MESSAGE = `<!DOCTYPE html>
|
|
197
201
|
</head>
|
198
202
|
<body>
|
199
203
|
<h3>Linny-R server (127.0.0.1) is shutting down</h3>
|
200
|
-
<p>To
|
201
|
-
|
202
|
-
|
204
|
+
<p>To restart Linny-R, switch to your ${SETTINGS.cli_name} window
|
205
|
+
and there at the prompt` +
|
206
|
+
(VERSION_INFO.up_to_date ? '' : `
|
207
|
+
first type:</p>
|
203
208
|
<p> <tt>npm update linny-r</tt><p>
|
204
|
-
|
205
|
-
|
209
|
+
to upgrade to Linny-R version ${VERSION_INFO.latest}, and then`) +
|
210
|
+
` type:</p>
|
211
|
+
<p> <tt>node node_modules\\linny-r\\server</tt></p>
|
206
212
|
<p>
|
207
213
|
Then switch back to this window, and click
|
208
214
|
<button type="button"
|
@@ -213,104 +219,6 @@ SHUTDOWN_MESSAGE = `<!DOCTYPE html>
|
|
213
219
|
</body>
|
214
220
|
</html>`;
|
215
221
|
|
216
|
-
|
217
|
-
function compareVersions(v1, v2) {
|
218
|
-
// Robust comparison of version numbers
|
219
|
-
nrs1 = (v1 + '.0.0.0').split('.');
|
220
|
-
nrs2 = (v2 + '.0.0.0').split('.');
|
221
|
-
for(i = 0; i < 4; i++) {
|
222
|
-
nrs1[i] = nrs1[i].padStart(6, '0');
|
223
|
-
nrs2[i] = nrs2[i].padStart(6, '0');
|
224
|
-
}
|
225
|
-
v1 = nrs1.slice(0, 4).join('.');
|
226
|
-
v2 = nrs2.slice(0, 4).join('.');
|
227
|
-
if(v1 > v2) return 1;
|
228
|
-
if(v1 < v2) return -1;
|
229
|
-
return 0;
|
230
|
-
}
|
231
|
-
|
232
|
-
function checkVersion(res, version) {
|
233
|
-
// Check whether current version is the most recent
|
234
|
-
console.log('Check version:', version);
|
235
|
-
if(!version) {
|
236
|
-
serveHTML(res, '<h3>No version number specified</h3>');
|
237
|
-
return;
|
238
|
-
}
|
239
|
-
version = version.split('-').pop();
|
240
|
-
getTextFromURL(PUBLIC_LINNY_R_URL + '/check-version/?info',
|
241
|
-
// The `on_ok` function
|
242
|
-
(data, res) => {
|
243
|
-
const
|
244
|
-
info = data.split('|');
|
245
|
-
// Should be [version, release date]
|
246
|
-
if(info.length === 2) {
|
247
|
-
if(compareVersions(version, info[0]) >= 0) {
|
248
|
-
message = UP_TO_DATE_MESSAGE.replace(
|
249
|
-
'%1%', info[0]).replace('%2%', info[1]);
|
250
|
-
} else {
|
251
|
-
message = DOWNLOAD_MESSAGE.replace(
|
252
|
-
'%1%', info[0]).replace('%2%', info[1]);
|
253
|
-
}
|
254
|
-
serveHTML(res, VERSION_MESSAGE.replace('%1%', message));
|
255
|
-
}
|
256
|
-
},
|
257
|
-
// The `on_error` function
|
258
|
-
(error, res) => {
|
259
|
-
console.log(error);
|
260
|
-
serveHTML(res, NO_INTERNET_MESSAGE);
|
261
|
-
},
|
262
|
-
// The response object
|
263
|
-
res);
|
264
|
-
}
|
265
|
-
|
266
|
-
function autoCheck(res) {
|
267
|
-
// Compares the version number in the static file `index.html`
|
268
|
-
// with the version number in the corresponding file on the official
|
269
|
-
// Linny-R website, and serves a status string that indicates whether
|
270
|
-
// a newer release is available
|
271
|
-
const gpath = path.join(MAIN_DIRECTORY, 'static', 'index.html');
|
272
|
-
// Read the globals script
|
273
|
-
fs.readFile(gpath, 'utf8', (err, data) => {
|
274
|
-
let v_match = null;
|
275
|
-
if(err) {
|
276
|
-
console.log('WARNING: Failed to read file', gpath);
|
277
|
-
} else {
|
278
|
-
// Extract the version number
|
279
|
-
v_match = data.match(/LINNY_R_VERSION = '(.+?)'/);
|
280
|
-
if(!v_match) console.log('WARNING: No version number found');
|
281
|
-
}
|
282
|
-
if(!v_match) {
|
283
|
-
servePlainText(res,'no version');
|
284
|
-
return;
|
285
|
-
}
|
286
|
-
let version = v_match[1];
|
287
|
-
// Get the current `index.html` file from the official Linny-R server
|
288
|
-
getTextFromURL(PUBLIC_LINNY_R_URL + '/check-version/?info',
|
289
|
-
// The `on_ok` function: compare versions and return status
|
290
|
-
(data, res) => {
|
291
|
-
let check = 'no match';
|
292
|
-
const
|
293
|
-
info = data.split('|');
|
294
|
-
// Should be [version, release date]
|
295
|
-
if(info.length === 2) {
|
296
|
-
if(compareVersions(version, info[0]) >= 0) {
|
297
|
-
check = 'up-to-date';
|
298
|
-
} else {
|
299
|
-
check = info[0] + '|' + info[1];
|
300
|
-
}
|
301
|
-
}
|
302
|
-
servePlainText(res, check);
|
303
|
-
},
|
304
|
-
// The `on_error` function
|
305
|
-
(error, res) => {
|
306
|
-
console.log(error);
|
307
|
-
servePlainText(res, 'no match');
|
308
|
-
},
|
309
|
-
// The response object
|
310
|
-
res);
|
311
|
-
});
|
312
|
-
}
|
313
|
-
|
314
222
|
// Repository functionality
|
315
223
|
// ========================
|
316
224
|
// For repository services, the Linny-R JavaScript application communicates with
|
@@ -1111,10 +1019,6 @@ function processRequest(req, res, cmd, data) {
|
|
1111
1019
|
SERVER.close();
|
1112
1020
|
} else if(cmd === '/auto-check') {
|
1113
1021
|
autoCheck(res);
|
1114
|
-
} else if(cmd === '/auto-update') {
|
1115
|
-
autoUpdate(res);
|
1116
|
-
} else if(cmd === '/check-version') {
|
1117
|
-
checkVersion(res, (new URLSearchParams(data)).get('v'));
|
1118
1022
|
} else if(cmd === '/repo/') {
|
1119
1023
|
repo(res, new URLSearchParams(data));
|
1120
1024
|
} else if(cmd === '/load-data/') {
|
@@ -1172,7 +1076,7 @@ function serveStaticFile(res, path) {
|
|
1172
1076
|
console.log('Static file:', path);
|
1173
1077
|
path = '/static' + path;
|
1174
1078
|
}
|
1175
|
-
fs.readFile(
|
1079
|
+
fs.readFile(MODULE_DIRECTORY + path, (err, data) => {
|
1176
1080
|
if(err) {
|
1177
1081
|
console.log(err);
|
1178
1082
|
res.writeHead(404);
|
@@ -1536,7 +1440,7 @@ function createLaunchScript() {
|
|
1536
1440
|
// Only write the script content if the file it does not yet exist
|
1537
1441
|
console.log('Creating launch script:', sp);
|
1538
1442
|
let code = lines.join(os.EOL);
|
1539
|
-
if(PLATFORM.startsWith('win')) code = code.
|
1443
|
+
if(PLATFORM.startsWith('win')) code = code.replaceAll('#', '::');
|
1540
1444
|
fs.writeFileSync(sp, code, 'utf8');
|
1541
1445
|
}
|
1542
1446
|
} catch(err) {
|
package/static/index.html
CHANGED
@@ -54,16 +54,17 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
54
54
|
<!-- NOTE: do not display the page until after software update check -->
|
55
55
|
<style>body { display: none; }</style>
|
56
56
|
<script>
|
57
|
+
// Create global variables
|
57
58
|
var
|
58
59
|
// NODE = false indicates that modules need not export their properties
|
59
60
|
NODE = false,
|
60
61
|
// Version number and release date
|
61
|
-
LINNY_R_VERSION = '
|
62
|
-
VERSION_RELEASE_DATE =
|
62
|
+
LINNY_R_VERSION = '0',
|
63
|
+
VERSION_RELEASE_DATE = 0,
|
63
64
|
// Linny-R server hosting public channels
|
64
65
|
PUBLIC_LINNY_R_URL = 'https://sysmod.tbm.tudelft.nl/linny-r',
|
65
66
|
// Create the XML parser
|
66
|
-
XML_PARSER = new DOMParser()
|
67
|
+
XML_PARSER = new DOMParser(),
|
67
68
|
// NOTE: global variables will be initialized when page has loaded
|
68
69
|
// The controller object (User Interface)
|
69
70
|
UI = null,
|
@@ -86,14 +87,14 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
86
87
|
// Stack for undo/redo operations
|
87
88
|
UNDO_STACK = null,
|
88
89
|
// The virtual machine
|
89
|
-
VM = null
|
90
|
+
VM = null,
|
90
91
|
|
91
92
|
// Load audio files
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
93
|
+
SOUNDS = {
|
94
|
+
notification: new Audio('sounds/notification.wav'),
|
95
|
+
warning: new Audio('sounds/warning.wav'),
|
96
|
+
error: new Audio('sounds/error.wav')
|
97
|
+
};
|
97
98
|
|
98
99
|
|
99
100
|
function loadLinnyR() {
|
@@ -149,14 +150,17 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
149
150
|
console.log('Version check:', data);
|
150
151
|
const info = data.split('|');
|
151
152
|
if(info.length > 1) {
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
153
|
+
LINNY_R_VERSION = info[0];
|
154
|
+
if(info[1] !== 'up-to-date') {
|
155
|
+
UI.check_update_modal.element('msg').innerHTML = [
|
156
|
+
'<a href="https://sysmod.tbm.tudelft.nl/linny-r/docs/?15" ',
|
157
|
+
'target="_blank">Version <strong>',
|
158
|
+
info[1], '</strong></a> released on ', info[2],
|
159
|
+
' can be installed.'].join('');
|
160
|
+
UI.check_update_modal.show();
|
161
|
+
UI.check_update_modal.element('buttons').style.display = 'block';
|
162
|
+
}
|
163
|
+
} else {
|
160
164
|
UI.warn('Version check failed: "' + data + '"');
|
161
165
|
}
|
162
166
|
})
|