linny-r 1.6.8 → 1.7.1
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 +7 -7
- package/console.js +250 -305
- package/package.json +1 -1
- package/server.js +40 -134
- package/static/index.html +64 -5
- package/static/linny-r.css +41 -0
- package/static/scripts/linny-r-ctrl.js +47 -42
- package/static/scripts/linny-r-gui-controller.js +153 -15
- package/static/scripts/linny-r-gui-monitor.js +21 -9
- package/static/scripts/linny-r-gui-paper.js +18 -18
- package/static/scripts/linny-r-gui-receiver.js +5 -5
- package/static/scripts/linny-r-milp.js +363 -188
- package/static/scripts/linny-r-model.js +43 -29
- package/static/scripts/linny-r-utils.js +20 -3
- package/static/scripts/linny-r-vm.js +162 -73
package/package.json
CHANGED
package/server.js
CHANGED
@@ -137,13 +137,13 @@ const MILPSolver = require('./static/scripts/linny-r-milp.js');
|
|
137
137
|
// - workspace=[path] will create workspace in [path] instead of (main)/user
|
138
138
|
const SETTINGS = commandLineSettings();
|
139
139
|
|
140
|
-
// The workspace defines the paths to directories where Linny-R can write files
|
140
|
+
// The workspace defines the paths to directories where Linny-R can write files.
|
141
141
|
const WORKSPACE = createWorkspace();
|
142
142
|
|
143
|
-
// Initialize the solver
|
144
|
-
const SOLVER = new MILPSolver(SETTINGS, WORKSPACE);
|
143
|
+
// Initialize the solver.
|
144
|
+
const SOLVER = new MILPSolver(SETTINGS.preferred_solver, WORKSPACE);
|
145
145
|
|
146
|
-
// Create launch script
|
146
|
+
// Create launch script.
|
147
147
|
createLaunchScript();
|
148
148
|
|
149
149
|
// Create the HTTP server.
|
@@ -979,7 +979,7 @@ function receiver(res, sp) {
|
|
979
979
|
rcvrAbort(res, rpath, rfile, sp.get('log') || 'NO EVENT LOG');
|
980
980
|
} else if(action === 'report') {
|
981
981
|
let run = sp.get('run');
|
982
|
-
// Zero-pad run number to permit sorting run report file names in sequence
|
982
|
+
// Zero-pad run number to permit sorting run report file names in sequence.
|
983
983
|
run = (run ? '-' + run.padStart(3, '0') : '');
|
984
984
|
let data = sp.get('data') || '',
|
985
985
|
stats = sp.get('stats') || '',
|
@@ -1262,11 +1262,24 @@ function processRequest(req, res, cmd, data) {
|
|
1262
1262
|
// NOTE: On remote servers, solver actions require authentication.
|
1263
1263
|
if(action === 'logon') {
|
1264
1264
|
// No authentication -- simply return the passed token, "local host"
|
1265
|
-
// as server name,
|
1266
|
-
|
1267
|
-
|
1265
|
+
// as server name, the name of the solver, the list of installed
|
1266
|
+
// solvers, and the Linny-R directory.
|
1267
|
+
serveJSON(res, {
|
1268
|
+
token: 'local host',
|
1269
|
+
server: 'local host',
|
1270
|
+
solver: SOLVER.id,
|
1271
|
+
solver_list: Object.keys(SOLVER.solver_list),
|
1272
|
+
path: WORKING_DIRECTORY
|
1273
|
+
});
|
1268
1274
|
} else if(action === 'png') {
|
1269
1275
|
convertSVGtoPNG(req, res, sp);
|
1276
|
+
} else if(action === 'change') {
|
1277
|
+
const sid = sp.get('solver');
|
1278
|
+
if(SOLVER.changeDefault(sid)) {
|
1279
|
+
servePlainText(res, 'Default solver set to ' + SOLVER.name);
|
1280
|
+
} else {
|
1281
|
+
servePlainText(res, 'WARNING: Failed to change solver to ' + sid);
|
1282
|
+
}
|
1270
1283
|
} else if(action === 'solve') {
|
1271
1284
|
serveJSON(res, SOLVER.solveBlock(sp));
|
1272
1285
|
} else {
|
@@ -1282,7 +1295,6 @@ function processRequest(req, res, cmd, data) {
|
|
1282
1295
|
serveHTML(res, SHUTDOWN_MESSAGE);
|
1283
1296
|
SERVER.close();
|
1284
1297
|
} else if(cmd === 'version') {
|
1285
|
-
logAction('HERE version = ' + VERSION_INFO.current);
|
1286
1298
|
servePlainText(res, 'Current version is ' + VERSION_INFO.current);
|
1287
1299
|
} else if(cmd === 'update') {
|
1288
1300
|
// Shut down this server silently. When the server was started from
|
@@ -1529,8 +1541,6 @@ function commandLineSettings() {
|
|
1529
1541
|
launch: false,
|
1530
1542
|
port: 5050,
|
1531
1543
|
preferred_solver: '',
|
1532
|
-
solver: '',
|
1533
|
-
solver_path: '',
|
1534
1544
|
user_dir: path.join(WORKING_DIRECTORY, 'user')
|
1535
1545
|
};
|
1536
1546
|
const
|
@@ -1600,142 +1610,36 @@ Possible options are:
|
|
1600
1610
|
}
|
1601
1611
|
}
|
1602
1612
|
}
|
1603
|
-
// Check whether
|
1613
|
+
// Check whether Inkscape has been installed.
|
1604
1614
|
const path_list = process.env.PATH.split(path.delimiter);
|
1605
|
-
let gurobi_path = '',
|
1606
|
-
scip_path = '',
|
1607
|
-
cplex_path = '',
|
1608
|
-
match,
|
1609
|
-
max_v = -1;
|
1610
1615
|
for(let i = 0; i < path_list.length; i++) {
|
1611
|
-
|
1612
|
-
if(match && parseInt(match[1]) > max_v) {
|
1613
|
-
gurobi_path = path_list[i];
|
1614
|
-
max_v = parseInt(match[1]);
|
1615
|
-
}
|
1616
|
-
match = path_list[i].match(/[\/\\]cplex[\/\\]bin/i);
|
1617
|
-
if(match) {
|
1618
|
-
cplex_path = path_list[i];
|
1619
|
-
} else {
|
1620
|
-
// NOTE: CPLEX may create its own environment variable for its paths
|
1621
|
-
match = path_list[i].match(/%(.*cplex.*)%/i);
|
1622
|
-
if(match) {
|
1623
|
-
const cpl = process.env[match[1]].split(path.delimiter);
|
1624
|
-
for(let i = 0; i < cpl.length; i++) {
|
1625
|
-
match = cpl[i].match(/[\/\\]cplex[\/\\]bin/i);
|
1626
|
-
if(match) {
|
1627
|
-
cplex_path = cpl[i];
|
1628
|
-
break;
|
1629
|
-
}
|
1630
|
-
}
|
1631
|
-
}
|
1632
|
-
}
|
1633
|
-
match = path_list[i].match(/[\/\\]scip[^\/\\]+[\/\\]bin/i);
|
1634
|
-
if(match) scip_path = path_list[i];
|
1616
|
+
// Check whether it is an Inkscape path.
|
1635
1617
|
match = path_list[i].match(/inkscape/i);
|
1618
|
+
// If so, use it (so the *last* matching path will be used).
|
1636
1619
|
if(match) settings.inkscape = path_list[i];
|
1637
1620
|
}
|
1638
|
-
|
1639
|
-
console.log('Looking for Gurobi in /usr/local/bin');
|
1640
|
-
try {
|
1641
|
-
// On macOS and Unix, Gurobi is in the user's local binaries
|
1642
|
-
const gp = '/usr/local/bin';
|
1643
|
-
fs.accessSync(gp + '/gurobi_cl');
|
1644
|
-
gurobi_path = gp;
|
1645
|
-
} catch(err) {
|
1646
|
-
// No real error, so no action needed
|
1647
|
-
}
|
1648
|
-
}
|
1649
|
-
if(gurobi_path) {
|
1650
|
-
console.log('Path to Gurobi:', gurobi_path);
|
1651
|
-
// Check if command line version is executable
|
1652
|
-
const sp = path.join(gurobi_path,
|
1653
|
-
'gurobi_cl' + (PLATFORM.startsWith('win') ? '.exe' : ''));
|
1654
|
-
try {
|
1655
|
-
fs.accessSync(sp, fs.constants.X_OK);
|
1656
|
-
if(settings.solver !== 'gurobi')
|
1657
|
-
settings.solver = 'gurobi';
|
1658
|
-
settings.solver_path = sp;
|
1659
|
-
} catch(err) {
|
1660
|
-
console.log(err.message);
|
1661
|
-
console.log(
|
1662
|
-
'WARNING: Failed to access the Gurobi command line application');
|
1663
|
-
}
|
1664
|
-
}
|
1665
|
-
// Check if cplex(.exe) exists in its directory
|
1666
|
-
let sp = path.join(cplex_path, 'cplex' + (PLATFORM.startsWith('win') ? '.exe' : ''));
|
1667
|
-
const need_cplex = !settings.solver || settings.preferred_solver === 'cplex';
|
1668
|
-
try {
|
1669
|
-
fs.accessSync(sp, fs.constants.X_OK);
|
1670
|
-
console.log('Path to CPLEX:', sp);
|
1671
|
-
if(need_cplex) {
|
1672
|
-
settings.solver = 'cplex';
|
1673
|
-
settings.solver_path = sp;
|
1674
|
-
}
|
1675
|
-
} catch(err) {
|
1676
|
-
// Only report error if CPLEX is needed
|
1677
|
-
if(need_cplex) {
|
1678
|
-
console.log(err.message);
|
1679
|
-
console.log('WARNING: CPLEX application not found in', sp);
|
1680
|
-
}
|
1681
|
-
}
|
1682
|
-
// Check if scip(.exe) exists in its directory
|
1683
|
-
sp = path.join(scip_path, 'scip' + (PLATFORM.startsWith('win') ? '.exe' : ''));
|
1684
|
-
const need_scip = !settings.solver || settings.preferred_solver === 'scip';
|
1685
|
-
try {
|
1686
|
-
fs.accessSync(sp, fs.constants.X_OK);
|
1687
|
-
console.log('Path to SCIP:', sp);
|
1688
|
-
if(need_scip) {
|
1689
|
-
settings.solver = 'scip';
|
1690
|
-
settings.solver_path = sp;
|
1691
|
-
}
|
1692
|
-
} catch(err) {
|
1693
|
-
// Only report error if SCIP is needed
|
1694
|
-
if(need_scip) {
|
1695
|
-
console.log(err.message);
|
1696
|
-
console.log('WARNING: SCIP application not found in', sp);
|
1697
|
-
}
|
1698
|
-
}
|
1699
|
-
// Check if lp_solve(.exe) exists in working directory
|
1700
|
-
sp = path.join(WORKING_DIRECTORY,
|
1701
|
-
'lp_solve' + (PLATFORM.startsWith('win') ? '.exe' : ''));
|
1702
|
-
const need_lps = !settings.solver || settings.preferred_solver === 'lp_solve';
|
1703
|
-
try {
|
1704
|
-
fs.accessSync(sp, fs.constants.X_OK);
|
1705
|
-
console.log('Path to LP_solve:', sp);
|
1706
|
-
if(need_lps) {
|
1707
|
-
settings.solver = 'lp_solve';
|
1708
|
-
settings.solver_path = sp;
|
1709
|
-
}
|
1710
|
-
} catch(err) {
|
1711
|
-
// Only report error if LP_solve is needed
|
1712
|
-
if(need_lps) {
|
1713
|
-
console.log(err.message);
|
1714
|
-
console.log('WARNING: LP_solve application not found in', sp);
|
1715
|
-
}
|
1716
|
-
}
|
1717
|
-
// On macOS, Inkscape is not added to the PATH environment variable
|
1621
|
+
// NOTE: On macOS, Inkscape is not added to the PATH environment variable.
|
1718
1622
|
if(!settings.inkscape && PLATFORM === 'darwin') {
|
1719
1623
|
console.log('Looking for Inkscape in Applications...');
|
1720
1624
|
try {
|
1721
|
-
// Look in the default directory
|
1625
|
+
// Look in the default directory.
|
1722
1626
|
const ip = '/Applications/Inkscape.app/Contents/MacOS';
|
1723
1627
|
fs.accessSync(ip);
|
1724
1628
|
settings.inkscape = ip;
|
1725
1629
|
} catch(err) {
|
1726
|
-
// No real error, so no action needed
|
1630
|
+
// No real error, so no action needed.
|
1727
1631
|
}
|
1728
1632
|
}
|
1729
|
-
// Verify that Inkscape is installed
|
1633
|
+
// Verify that Inkscape is installed.
|
1730
1634
|
if(settings.inkscape) {
|
1731
|
-
// NOTE: On Windows, the command line version is a .com file
|
1635
|
+
// NOTE: On Windows, the command line version is a .com file.
|
1732
1636
|
let ip = path.join(settings.inkscape,
|
1733
1637
|
'inkscape' + (PLATFORM.startsWith('win') ? '.com' : ''));
|
1734
1638
|
try {
|
1735
1639
|
fs.accessSync(ip, fs.constants.X_OK);
|
1736
1640
|
} catch(err) {
|
1737
1641
|
// NOTE: As of Inkscape version 1.3, the command line executable
|
1738
|
-
// is called inkscapecom(.com)
|
1642
|
+
// is called inkscapecom(.com).
|
1739
1643
|
ip = path.join(settings.inkscape,
|
1740
1644
|
'inkscapecom' + (PLATFORM.startsWith('win') ? '.com' : ''));
|
1741
1645
|
}
|
@@ -1759,14 +1663,14 @@ Possible options are:
|
|
1759
1663
|
}
|
1760
1664
|
|
1761
1665
|
function createWorkspace() {
|
1762
|
-
//
|
1763
|
-
// paths to sub-directories, and
|
1666
|
+
// Verify that Linny-R has write access to the user workspace, define
|
1667
|
+
// paths to sub-directories, and create them if necessary.
|
1764
1668
|
try {
|
1765
|
-
// See whether the user directory already exists
|
1669
|
+
// See whether the user directory already exists.
|
1766
1670
|
try {
|
1767
1671
|
fs.accessSync(SETTINGS.user_dir, fs.constants.R_OK | fs.constants.W_O);
|
1768
1672
|
} catch(err) {
|
1769
|
-
// If not, try to create it
|
1673
|
+
// If not, try to create it.
|
1770
1674
|
fs.mkdirSync(SETTINGS.user_dir);
|
1771
1675
|
console.log('Created user directory:', SETTINGS.user_dir);
|
1772
1676
|
}
|
@@ -1776,7 +1680,7 @@ function createWorkspace() {
|
|
1776
1680
|
SETTINGS.user_dir);
|
1777
1681
|
process.exit();
|
1778
1682
|
}
|
1779
|
-
// Define the sub-directory paths
|
1683
|
+
// Define the sub-directory paths.
|
1780
1684
|
const ws = {
|
1781
1685
|
autosave: path.join(SETTINGS.user_dir, 'autosave'),
|
1782
1686
|
channel: path.join(SETTINGS.user_dir, 'channel'),
|
@@ -1787,7 +1691,7 @@ function createWorkspace() {
|
|
1787
1691
|
reports: path.join(SETTINGS.user_dir, 'reports'),
|
1788
1692
|
solver_output: path.join(SETTINGS.user_dir, 'solver')
|
1789
1693
|
};
|
1790
|
-
// Create these sub-directories if not aready there
|
1694
|
+
// Create these sub-directories if not aready there.
|
1791
1695
|
try {
|
1792
1696
|
for(let p in ws) if(ws.hasOwnProperty(p)) {
|
1793
1697
|
try {
|
@@ -1801,9 +1705,11 @@ function createWorkspace() {
|
|
1801
1705
|
console.log(err.message);
|
1802
1706
|
console.log('WARNING: No access to workspace directory');
|
1803
1707
|
}
|
1804
|
-
// The file containing name, URL and access token for remote repositories
|
1708
|
+
// The file containing name, URL and access token for remote repositories.
|
1805
1709
|
ws.repositories = path.join(SETTINGS.user_dir, 'repositories.cfg');
|
1806
|
-
//
|
1710
|
+
// For completeness, add path to Linny-R directory.
|
1711
|
+
ws.working_directory = WORKING_DIRECTORY;
|
1712
|
+
// Return the updated workspace object.
|
1807
1713
|
return ws;
|
1808
1714
|
}
|
1809
1715
|
|
package/static/index.html
CHANGED
@@ -162,16 +162,17 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
162
162
|
if(info.length > 1) {
|
163
163
|
LINNY_R_VERSION = info[0];
|
164
164
|
const v = 'Version ' + LINNY_R_VERSION;
|
165
|
-
// Update the "home page" of the documentation manager
|
165
|
+
// Update the "home page" of the documentation manager.
|
166
166
|
DOCUMENTATION_MANAGER.about_linny_r =
|
167
167
|
DOCUMENTATION_MANAGER.about_linny_r.replace(
|
168
168
|
'[LINNY_R_VERSION]', v);
|
169
|
-
// Update the version number in the browser's upper left corner
|
169
|
+
// Update the version number in the browser's upper left corner.
|
170
170
|
document.getElementById('linny-r-version-number').innerHTML = v;
|
171
|
-
// NOTE:
|
172
|
-
// not reached; if so, do not suggest a new version exists
|
171
|
+
// NOTE: Server detects "version 0" when npmjs website was
|
172
|
+
// not reached; if so, do not suggest a new version exists.
|
173
173
|
if(info[1] !== 'up-to-date' && info[1] !== '0') {
|
174
|
-
// Inform user that newer version exists
|
174
|
+
// Inform user that newer version exists.
|
175
|
+
UI.newer_version = info[1];
|
175
176
|
UI.check_update_modal.element('msg').innerHTML = [
|
176
177
|
'<a href="', GITHUB_REPOSITORY,
|
177
178
|
'/releases/tag/v', info[1], '" ',
|
@@ -423,6 +424,28 @@ and move the cursor over the status bar">
|
|
423
424
|
</div>
|
424
425
|
</div>
|
425
426
|
|
427
|
+
<!-- the SERVER dialog permits solver selection, update and shut-down -->
|
428
|
+
<div id="server-modal" class="modal">
|
429
|
+
<div id="server-dlg" class="inp-dlg">
|
430
|
+
<div class="dlg-title">
|
431
|
+
Server and solver
|
432
|
+
<img class="cancel-btn" src="images/cancel.png">
|
433
|
+
<img class="ok-btn" src="images/ok.png">
|
434
|
+
</div>
|
435
|
+
<div id="server-host-div" title="Linny-R directory">Server on local host</div>
|
436
|
+
<div id="server-no-solver-div">No solver detected</div>
|
437
|
+
<div id="server-solver-div" hidden="hidden">
|
438
|
+
<div id="server-sel">
|
439
|
+
<label>Default solver:</label>
|
440
|
+
<select id="server-solver">
|
441
|
+
</select>
|
442
|
+
</div>
|
443
|
+
</div>
|
444
|
+
<button id="server-update" hidden="hidden">Update Linny-R to new version</button>
|
445
|
+
<button id="server-shut-down">Shut down server</button>
|
446
|
+
</div>
|
447
|
+
</div>
|
448
|
+
|
426
449
|
<!-- the MODEL dialog prompts for (optional) model name and author name -->
|
427
450
|
<div id="model-modal" class="modal">
|
428
451
|
<div id="model-dlg" class="inp-dlg">
|
@@ -605,6 +628,8 @@ and move the cursor over the status bar">
|
|
605
628
|
<td colspan="2">Solver time limit:
|
606
629
|
<input id="settings-time-limit" type="text" autocomplete="off"
|
607
630
|
style="width:35px;text-align:center"> seconds per block
|
631
|
+
<img id="settings-solver-prefs-btn" class="sbtn" src="images/settings.png"
|
632
|
+
title="Set solver preferences">
|
608
633
|
</td>
|
609
634
|
</tr>
|
610
635
|
<tr>
|
@@ -688,6 +713,40 @@ NOTE: Unit symbols are case-sensitive, so BTU ≠ Btu">
|
|
688
713
|
</div>
|
689
714
|
</div>
|
690
715
|
|
716
|
+
<!-- the SOLVER dialog permits specifying solver preference and tolerances -->
|
717
|
+
<div id="solver-modal" class="modal">
|
718
|
+
<div id="solver-dlg" class="inp-dlg">
|
719
|
+
<div class="dlg-title">
|
720
|
+
Solver preferences
|
721
|
+
<img class="cancel-btn" src="images/cancel.png">
|
722
|
+
<img class="ok-btn" src="images/ok.png">
|
723
|
+
</div>
|
724
|
+
<table style="width: 100%">
|
725
|
+
<tr title="This solver will be used if it is installed">
|
726
|
+
<td>
|
727
|
+
<label>Preferred solver:</label>
|
728
|
+
<select id="solver-preference">
|
729
|
+
</select>
|
730
|
+
</td>
|
731
|
+
</tr>
|
732
|
+
<tr title="Tolerance may range from 1e-9 to 0.1">
|
733
|
+
<td>
|
734
|
+
<label>Integer feasibility tolerance:</label>
|
735
|
+
<input id="solver-int-feasibility" style="width: 55px"
|
736
|
+
placeholder="5e-7" type="text" autocomplete="off">
|
737
|
+
</td>
|
738
|
+
</tr>
|
739
|
+
<tr title="Relative gap may range from 0 to 0.5">
|
740
|
+
<td>
|
741
|
+
<label>Relative MIP gap:</label>
|
742
|
+
<input id="solver-mip-gap" style="width: 55px"
|
743
|
+
placeholder="1e-4" type="text" autocomplete="off">
|
744
|
+
</td>
|
745
|
+
</tr>
|
746
|
+
</table>
|
747
|
+
</div>
|
748
|
+
</div>
|
749
|
+
|
691
750
|
<!-- the REPOSITORY BROWSER dialog allows interaction with repositories -->
|
692
751
|
<div id="repository-dlg" class="inp-dlg">
|
693
752
|
<div id="repository-hdr" class="dragger dlg-title">Repository browser
|
package/static/linny-r.css
CHANGED
@@ -944,6 +944,7 @@ img.inline-cancel-btn {
|
|
944
944
|
height: min-content;
|
945
945
|
}
|
946
946
|
|
947
|
+
#settings-solver-prefs-btn,
|
947
948
|
#settings-scale-units-btn {
|
948
949
|
margin-left: 0px;
|
949
950
|
margin-right: 7px;
|
@@ -952,6 +953,7 @@ img.inline-cancel-btn {
|
|
952
953
|
filter: brightness(85%);
|
953
954
|
}
|
954
955
|
|
956
|
+
#settings-solver-prefs-btn:hover,
|
955
957
|
#settings-scale-units-btn:hover {
|
956
958
|
filter: brightness(200%);
|
957
959
|
}
|
@@ -994,6 +996,16 @@ input.pws-5 {
|
|
994
996
|
background-color: #c0ffc0;
|
995
997
|
}
|
996
998
|
|
999
|
+
/* styles for the SOLVER PREFERENCES dialog */
|
1000
|
+
#solver-modal {
|
1001
|
+
z-index: 110; /* on top of the SETTINGS dialog */
|
1002
|
+
}
|
1003
|
+
|
1004
|
+
#solver-dlg {
|
1005
|
+
width: 212px;
|
1006
|
+
height: min-content;
|
1007
|
+
}
|
1008
|
+
|
997
1009
|
/* styles for the ACTOR DIALOG
|
998
1010
|
NOTE: the z-index is very high for this dialog, as it should appear
|
999
1011
|
on top of the ACTOR LIST DIALOG
|
@@ -1190,6 +1202,35 @@ td.export {
|
|
1190
1202
|
color: #0000b0;
|
1191
1203
|
}
|
1192
1204
|
|
1205
|
+
/* styles for the SERVER dialog */
|
1206
|
+
#server-dlg {
|
1207
|
+
width: 200px;
|
1208
|
+
height: min-content;
|
1209
|
+
}
|
1210
|
+
#server-host-div,
|
1211
|
+
#server-solver-div {
|
1212
|
+
margin: 3px;
|
1213
|
+
}
|
1214
|
+
|
1215
|
+
#server-no-solver-div {
|
1216
|
+
margin: 3px;
|
1217
|
+
padding-top: 2px;
|
1218
|
+
padding-bottom: 2px;
|
1219
|
+
font-style: italic;
|
1220
|
+
margin-bottom: 2px;
|
1221
|
+
}
|
1222
|
+
|
1223
|
+
#server-solver {
|
1224
|
+
font-size: 12px;
|
1225
|
+
margin-bottom: 3px;
|
1226
|
+
}
|
1227
|
+
|
1228
|
+
#server-update,
|
1229
|
+
#server-shut-down {
|
1230
|
+
margin: 3px;
|
1231
|
+
font-size: 12px;
|
1232
|
+
}
|
1233
|
+
|
1193
1234
|
/* styles for the MODEL dialog */
|
1194
1235
|
#model-dlg {
|
1195
1236
|
width: 270px;
|