fhirsmith 0.7.5 → 0.8.0
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/CHANGELOG.md +50 -0
- package/README.md +8 -0
- package/library/html.js +4 -0
- package/library/languages.js +10 -0
- package/package.json +1 -1
- package/packages/package-crawler.js +106 -51
- package/packages/packages.js +14 -0
- package/publisher/publisher.js +118 -28
- package/registry/registry.js +99 -91
- package/root-bare-template.html +92 -0
- package/security.md +32 -0
- package/server.js +99 -22
- package/stats.js +43 -10
- package/tx/README.md +6 -6
- package/tx/cs/cs-api.js +3 -0
- package/tx/cs/cs-api.md +285 -0
- package/tx/cs/cs-loinc.js +14 -2
- package/tx/cs/cs-rxnorm.js +14 -10
- package/tx/cs/cs-snomed.js +166 -5
- package/tx/html/dash-metrics.liquid +147 -0
- package/tx/importers/import-rxnorm.module.js +4 -30
- package/tx/importers/readme.md +3 -1
- package/tx/library/canonical-resource.js +8 -0
- package/tx/library/conceptmap.js +3 -1
- package/tx/library/designations.js +4 -8
- package/tx/library/renderer.js +9 -9
- package/tx/library.js +10 -4
- package/tx/ocl/cm-ocl.cjs +185 -65
- package/tx/ocl/cs-ocl.cjs +69 -50
- package/tx/ocl/jobs/background-queue.cjs +0 -8
- package/tx/ocl/mappers/concept-mapper.cjs +13 -3
- package/tx/ocl/shared/patches.cjs +1 -0
- package/tx/ocl/vs-ocl.cjs +137 -157
- package/tx/operation-context.js +3 -3
- package/tx/provider.js +4 -3
- package/tx/sct/structures.js +5 -0
- package/tx/tx-html.js +36 -9
- package/tx/tx.fhir.org.yml +1 -1
- package/tx/tx.js +34 -11
- package/tx/vs/vs-database.js +127 -6
- package/tx/vs/vs-vsac.js +98 -3
- package/tx/workers/search.js +2 -1
- package/tx/workers/translate.js +39 -14
- package/tx/workers/validate.js +3 -3
- package/utilities/dashboard.html +274 -0
- package/xig/xig.js +171 -9
package/server.js
CHANGED
|
@@ -269,7 +269,6 @@ async function loadTemplates() {
|
|
|
269
269
|
|
|
270
270
|
async function buildRootPageContent() {
|
|
271
271
|
stats.requestCount++;
|
|
272
|
-
let mc = 0;
|
|
273
272
|
let content = '<div class="row mb-4">';
|
|
274
273
|
content += '<div class="col-12">';
|
|
275
274
|
|
|
@@ -278,35 +277,30 @@ async function buildRootPageContent() {
|
|
|
278
277
|
|
|
279
278
|
// Check which modules are enabled and add them to the list
|
|
280
279
|
if (config.modules.packages.enabled) {
|
|
281
|
-
mc++;
|
|
282
280
|
content += '<li class="list-group-item">';
|
|
283
281
|
content += '<a href="/packages" class="text-decoration-none">Package Server</a>: Browse and download FHIR Implementation Guide packages';
|
|
284
282
|
content += '</li>';
|
|
285
283
|
}
|
|
286
284
|
|
|
287
285
|
if (config.modules.xig.enabled) {
|
|
288
|
-
mc++;
|
|
289
286
|
content += '<li class="list-group-item">';
|
|
290
287
|
content += '<a href="/xig" class="text-decoration-none">FHIR IG Statistics</a>: Statistics and analysis of FHIR Implementation Guides';
|
|
291
288
|
content += '</li>';
|
|
292
289
|
}
|
|
293
290
|
|
|
294
291
|
if (config.modules.shl.enabled) {
|
|
295
|
-
mc++;
|
|
296
292
|
content += '<li class="list-group-item">';
|
|
297
293
|
content += '<a href="/shl" class="text-decoration-none">SHL Server</a>: SMART Health Links management and validation';
|
|
298
294
|
content += '</li>';
|
|
299
295
|
}
|
|
300
296
|
|
|
301
297
|
if (config.modules.vcl.enabled) {
|
|
302
|
-
mc++;
|
|
303
298
|
content += '<li class="list-group-item">';
|
|
304
299
|
content += '<a href="/VCL" class="text-decoration-none">VCL Server</a>: ValueSet Compose Language expression parsing';
|
|
305
300
|
content += '</li>';
|
|
306
301
|
}
|
|
307
302
|
|
|
308
303
|
if (config.modules.registry && config.modules.registry.enabled) {
|
|
309
|
-
mc++;
|
|
310
304
|
content += '<li class="list-group-item">';
|
|
311
305
|
content += '<a href="/tx-reg" class="text-decoration-none">Terminology Server Registry</a>: ';
|
|
312
306
|
content += 'Discover and query FHIR terminology servers for code system and value set support';
|
|
@@ -314,7 +308,6 @@ async function buildRootPageContent() {
|
|
|
314
308
|
}
|
|
315
309
|
|
|
316
310
|
if (config.modules.publisher && config.modules.publisher.enabled) {
|
|
317
|
-
mc++;
|
|
318
311
|
content += '<li class="list-group-item">';
|
|
319
312
|
content += '<a href="/publisher" class="text-decoration-none">FHIR Publisher</a>: ';
|
|
320
313
|
content += 'Manage FHIR Implementation Guide publication tasks and approvals';
|
|
@@ -322,7 +315,6 @@ async function buildRootPageContent() {
|
|
|
322
315
|
}
|
|
323
316
|
|
|
324
317
|
if (config.modules.token && config.modules.token.enabled) {
|
|
325
|
-
mc++;
|
|
326
318
|
content += '<li class="list-group-item">';
|
|
327
319
|
content += '<a href="/token" class="text-decoration-none">Token Server</a>: ';
|
|
328
320
|
content += 'OAuth authentication and API key management for FHIR services';
|
|
@@ -330,7 +322,6 @@ async function buildRootPageContent() {
|
|
|
330
322
|
}
|
|
331
323
|
|
|
332
324
|
if (config.modules.npmprojector && config.modules.npmprojector.enabled) {
|
|
333
|
-
mc++;
|
|
334
325
|
content += '<li class="list-group-item">';
|
|
335
326
|
content += '<a href="/npmprojector" class="text-decoration-none">NpmProjector</a>: ';
|
|
336
327
|
content += 'Hot-reloading FHIR server with FHIRPath-based search indexes';
|
|
@@ -338,7 +329,6 @@ async function buildRootPageContent() {
|
|
|
338
329
|
}
|
|
339
330
|
|
|
340
331
|
if (config.modules?.['ext-tracker']?.enabled) {
|
|
341
|
-
mc++;
|
|
342
332
|
content += '<li class="list-group-item">';
|
|
343
333
|
content += '<a href="/ext-tracker" class="text-decoration-none">Extension Tracker</a>: ';
|
|
344
334
|
content += 'View of Extension Usage';
|
|
@@ -353,7 +343,6 @@ async function buildRootPageContent() {
|
|
|
353
343
|
content += '<ul class="mt-2 mb-0">';
|
|
354
344
|
for (const fc of folders) {
|
|
355
345
|
if (fc.enabled === false) continue;
|
|
356
|
-
mc++;
|
|
357
346
|
content += '<li>';
|
|
358
347
|
content += `<a href="${fc.url}" class="text-decoration-none">${fc.name}</a>: `;
|
|
359
348
|
content += 'File folder with write control';
|
|
@@ -370,7 +359,6 @@ async function buildRootPageContent() {
|
|
|
370
359
|
if (config.modules.tx.endpoints && config.modules.tx.endpoints.length > 0) {
|
|
371
360
|
content += '<ul class="mt-2 mb-0">';
|
|
372
361
|
for (const endpoint of config.modules.tx.endpoints) {
|
|
373
|
-
mc++;
|
|
374
362
|
content += `<li><a href="${endpoint.path}" class="text-decoration-none">${endpoint.path}</a> (FHIR v${endpoint.fhirVersion}${endpoint.context ? ', context: ' + endpoint.context : ''})</li>`;
|
|
375
363
|
}
|
|
376
364
|
content += '</ul>';
|
|
@@ -382,7 +370,6 @@ async function buildRootPageContent() {
|
|
|
382
370
|
|
|
383
371
|
content += '<hr/>';
|
|
384
372
|
|
|
385
|
-
|
|
386
373
|
// Calculate uptime
|
|
387
374
|
const uptimeMs = Date.now() - stats.startTime;
|
|
388
375
|
const uptimeSeconds = Math.floor(uptimeMs / 1000);
|
|
@@ -399,7 +386,7 @@ async function buildRootPageContent() {
|
|
|
399
386
|
// Memory usage
|
|
400
387
|
const memUsage = process.memoryUsage();
|
|
401
388
|
const heapUsedMB = (memUsage.heapUsed / 1024 / 1024).toFixed(2);
|
|
402
|
-
const
|
|
389
|
+
const heapAvailableMB = ((memUsage.heapTotal - memUsage.heapUsed) / 1024 / 1024).toFixed(2);
|
|
403
390
|
const rssMB = (memUsage.rss / 1024 / 1024).toFixed(2);
|
|
404
391
|
const freeMemMB = (os.freemem() / 1024 / 1024).toFixed(0);
|
|
405
392
|
const totalMemMB = (os.totalmem() / 1024 / 1024).toFixed(0);
|
|
@@ -407,20 +394,18 @@ async function buildRootPageContent() {
|
|
|
407
394
|
content += '<table class="grid">';
|
|
408
395
|
content += '<tr>';
|
|
409
396
|
content += `<td><strong>Uptime:</strong> ${escape(uptimeStr)}</td>`;
|
|
410
|
-
content += `<td><strong>Request Count:</strong> ${stats.requestCount}</td>`;
|
|
397
|
+
content += `<td><strong>Request Count:</strong> ${stats.requestCount} (static: ${stats.staticRequestCount})</td>`;
|
|
411
398
|
content += `<td><strong>Free Memory:</strong> ${freeMemMB} MB of ${totalMemMB} MB</td>`;
|
|
412
399
|
content += '</tr>';
|
|
413
400
|
content += '<tr>';
|
|
414
401
|
content += `<td><strong>Heap Used:</strong> ${heapUsedMB} MB</td>`;
|
|
415
|
-
content += `<td><strong>Heap
|
|
402
|
+
content += `<td><strong>Heap Available:</strong> ${heapAvailableMB} MB</td>`;
|
|
416
403
|
content += `<td><strong>Process Memory:</strong> ${rssMB} MB</td>`;
|
|
417
404
|
content += '</tr>';
|
|
418
405
|
content += getLogStats();
|
|
419
406
|
content += '</table>';
|
|
420
407
|
|
|
421
|
-
|
|
422
408
|
// ===== Metrics Graphs =====
|
|
423
|
-
|
|
424
409
|
const liquid = new Liquid({
|
|
425
410
|
root: path.join(__dirname, 'tx', 'html'),
|
|
426
411
|
extname: '.liquid'
|
|
@@ -468,6 +453,10 @@ app.get('/', async (req, res) => {
|
|
|
468
453
|
const templatePath = path.join(__dirname, 'root-template.html');
|
|
469
454
|
htmlServer.loadTemplate('root', templatePath);
|
|
470
455
|
}
|
|
456
|
+
if (!htmlServer.hasTemplate('root-bare')) {
|
|
457
|
+
const templatePath = path.join(__dirname, 'root-bare-template.html');
|
|
458
|
+
htmlServer.loadTemplate('root-bare', templatePath);
|
|
459
|
+
}
|
|
471
460
|
|
|
472
461
|
const content = await buildRootPageContent();
|
|
473
462
|
|
|
@@ -503,6 +492,15 @@ app.get('/', async (req, res) => {
|
|
|
503
492
|
app.get('/fhirsmith', (req, res) => serveFhirsmithHome(req, res));
|
|
504
493
|
|
|
505
494
|
// Serve static files
|
|
495
|
+
// Count static file hits separately from API/page requests
|
|
496
|
+
app.use((req, res, next) => {
|
|
497
|
+
res.on('finish', () => {
|
|
498
|
+
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
499
|
+
stats.staticRequestCount++;
|
|
500
|
+
}
|
|
501
|
+
});
|
|
502
|
+
next();
|
|
503
|
+
});
|
|
506
504
|
if (config.server?.webBase) {
|
|
507
505
|
const overrideDir = path.resolve(config.server.webBase);
|
|
508
506
|
app.use((req, res, next) => {
|
|
@@ -518,6 +516,85 @@ if (config.server?.webBase) {
|
|
|
518
516
|
}
|
|
519
517
|
app.use(express.static(path.join(__dirname, 'static')));
|
|
520
518
|
|
|
519
|
+
// Dashboard endpoint - server name, stats, graphs, and background tasks (no modules list)
|
|
520
|
+
app.get('/dashboard', async (req, res) => {
|
|
521
|
+
stats.requestCount++;
|
|
522
|
+
try {
|
|
523
|
+
if (!htmlServer.hasTemplate('root-bare')) {
|
|
524
|
+
const templatePath = path.join(__dirname, 'root-bare-template.html');
|
|
525
|
+
htmlServer.loadTemplate('root-bare', templatePath);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
const startTime = Date.now();
|
|
529
|
+
// Calculate uptime
|
|
530
|
+
const uptimeMs = Date.now() - stats.startTime;
|
|
531
|
+
const uptimeSeconds = Math.floor(uptimeMs / 1000);
|
|
532
|
+
const uptimeDays = Math.floor(uptimeSeconds / 86400);
|
|
533
|
+
const uptimeHours = Math.floor((uptimeSeconds % 86400) / 3600);
|
|
534
|
+
const uptimeMinutes = Math.floor((uptimeSeconds % 3600) / 60);
|
|
535
|
+
const uptimeSecs = uptimeSeconds % 60;
|
|
536
|
+
let uptimeStr = '';
|
|
537
|
+
if (uptimeDays > 0) uptimeStr += `${uptimeDays}d `;
|
|
538
|
+
if (uptimeHours > 0 || uptimeDays > 0) uptimeStr += `${uptimeHours}h `;
|
|
539
|
+
if (uptimeMinutes > 0 || uptimeHours > 0 || uptimeDays > 0) uptimeStr += `${uptimeMinutes}m `;
|
|
540
|
+
uptimeStr += `${uptimeSecs}s`;
|
|
541
|
+
|
|
542
|
+
// Memory usage
|
|
543
|
+
const memUsage = process.memoryUsage();
|
|
544
|
+
const heapUsedPCT = (memUsage.heapUsed * 100) / memUsage.heapTotal;
|
|
545
|
+
const freeMemMB = (os.freemem() / 1024 / 1024).toFixed(0);
|
|
546
|
+
const totalMemMB = (os.totalmem() / 1024 / 1024).toFixed(0);
|
|
547
|
+
const usedMemPCT = 100 - ((freeMemMB * 100) / totalMemMB);
|
|
548
|
+
const fstats = fs.statfsSync(folders.logsDir());
|
|
549
|
+
const diskPCT = (fstats.bavail * 100) / fstats.blocks;
|
|
550
|
+
|
|
551
|
+
let content = '';
|
|
552
|
+
content += '<table border="1">';
|
|
553
|
+
content += '<tr>';
|
|
554
|
+
content += `<td><strong>Uptime:</strong> ${escape(uptimeStr)}</td>`;
|
|
555
|
+
content += `<td><strong>Request Count:</strong> ${stats.requestCount} (static: ${stats.staticRequestCount})</td>`;
|
|
556
|
+
content += `<td style="background-color:${pctColor(usedMemPCT)}"><strong>Memory:</strong> ${usedMemPCT.toFixed(0)}%</td>`;
|
|
557
|
+
content += `<td style="background-color:${pctColor(heapUsedPCT)}"><strong>Heap:</strong> ${heapUsedPCT.toFixed(0)}%</td>`;
|
|
558
|
+
content += `<td style="background-color:${pctColor(diskPCT)}"><strong>Disk:</strong> ${diskPCT.toFixed(0)}%</td>`;
|
|
559
|
+
content += '</tr>';
|
|
560
|
+
content += '</table>';
|
|
561
|
+
|
|
562
|
+
// ===== Metrics Graphs =====
|
|
563
|
+
const liquid = new Liquid({
|
|
564
|
+
root: path.join(__dirname, 'tx', 'html'),
|
|
565
|
+
extname: '.liquid'
|
|
566
|
+
});
|
|
567
|
+
content += await liquid.renderFile('dash-metrics', {
|
|
568
|
+
historyJson: JSON.stringify(stats.history),
|
|
569
|
+
startTime: stats.startTime
|
|
570
|
+
});
|
|
571
|
+
content += stats.taskDetails();
|
|
572
|
+
content += `<p>Data: ${folders.dataDir()}</p>`;
|
|
573
|
+
|
|
574
|
+
content = '<div class="row mb-4"><div class="col-12">' + content + '</div></div>';
|
|
575
|
+
|
|
576
|
+
const pageStats = {
|
|
577
|
+
version: packageJson.version,
|
|
578
|
+
enabledModules: Object.keys(config.modules).filter(m => config.modules[m].enabled).length,
|
|
579
|
+
processingTime: Date.now() - startTime
|
|
580
|
+
};
|
|
581
|
+
|
|
582
|
+
const title = (config.hostName ? escape(config.hostName) : 'FHIRsmith Server')+' v'+packageJson.version;
|
|
583
|
+
const html = htmlServer.renderPage('root-bare', title, content, pageStats);
|
|
584
|
+
res.setHeader('Content-Type', 'text/html');
|
|
585
|
+
res.send(html);
|
|
586
|
+
} catch (error) {
|
|
587
|
+
serverLog.error('Error rendering dashboard:', error);
|
|
588
|
+
htmlServer.sendErrorResponse(res, 'root', error);
|
|
589
|
+
}
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
function pctColor(pct) {
|
|
593
|
+
const r = Math.round(pct * 2.55);
|
|
594
|
+
const g = Math.round((100 - pct) * 2.55);
|
|
595
|
+
return `rgb(${r}, ${g}, 100)`; // the 100 keeps it pastel/light
|
|
596
|
+
}
|
|
597
|
+
|
|
521
598
|
// Health check endpoint
|
|
522
599
|
app.get('/health', async (req, res) => {
|
|
523
600
|
const healthStatus = {
|
|
@@ -564,10 +641,10 @@ function getLogStats() {
|
|
|
564
641
|
let diskInfo = '';
|
|
565
642
|
try {
|
|
566
643
|
// statfs available in Node 18.15+
|
|
567
|
-
const
|
|
568
|
-
const blockSize =
|
|
569
|
-
const freeSpace =
|
|
570
|
-
const totalSpace =
|
|
644
|
+
const fstats = fs.statfsSync(logDir);
|
|
645
|
+
const blockSize = fstats.bsize;
|
|
646
|
+
const freeSpace = fstats.bavail * blockSize;
|
|
647
|
+
const totalSpace = fstats.blocks * blockSize;
|
|
571
648
|
const freeGB = (freeSpace / 1024 / 1024 / 1024).toFixed(2);
|
|
572
649
|
const totalGB = (totalSpace / 1024 / 1024 / 1024).toFixed(2);
|
|
573
650
|
diskInfo = `<td><strong>Disk Space:</strong> ${freeGB} GB of ${totalGB} GB</td>`;
|
package/stats.js
CHANGED
|
@@ -5,6 +5,7 @@ const escape = require('escape-html');
|
|
|
5
5
|
class ServerStats {
|
|
6
6
|
started = false;
|
|
7
7
|
requestCount = 0;
|
|
8
|
+
staticRequestCount = 0;
|
|
8
9
|
requestTime = 0;
|
|
9
10
|
// Collect metrics every 10 minutes
|
|
10
11
|
intervalMs = 10 * 60 * 1000;
|
|
@@ -27,7 +28,8 @@ class ServerStats {
|
|
|
27
28
|
const now = Date.now();
|
|
28
29
|
|
|
29
30
|
const currentMem = process.memoryUsage().heapUsed;
|
|
30
|
-
const
|
|
31
|
+
const combinedCount = this.requestCount + this.staticRequestCount;
|
|
32
|
+
const requestsDelta = combinedCount - this.requestCountSnapshot;
|
|
31
33
|
const requestsTat = requestsDelta > 0 ? this.requestTime / requestsDelta : 0;
|
|
32
34
|
const minutesSinceStart = this.history.length > 1
|
|
33
35
|
? this.intervalMs / 60000
|
|
@@ -38,7 +40,7 @@ class ServerStats {
|
|
|
38
40
|
const idleDelta = currentCpu.idle - this.lastUsage.idle;
|
|
39
41
|
const totalDelta = currentCpu.total - this.lastUsage.total;
|
|
40
42
|
const percent = totalDelta > 0 ? 100 * (1 - idleDelta / totalDelta) : 0;
|
|
41
|
-
|
|
43
|
+
|
|
42
44
|
const loopDelay = this.eventLoopMonitor.mean / 1e6;
|
|
43
45
|
let cacheCount = 0;
|
|
44
46
|
for (let m of this.cachingModules) {
|
|
@@ -48,11 +50,11 @@ class ServerStats {
|
|
|
48
50
|
this.history.push({time: now, mem: currentMem - this.startMem, rpm: requestsPerMin, tat: requestsTat, cpu: percent, block: loopDelay, cache : cacheCount});
|
|
49
51
|
|
|
50
52
|
this.eventLoopMonitor.reset();
|
|
51
|
-
this.requestCountSnapshot =
|
|
53
|
+
this.requestCountSnapshot = combinedCount;
|
|
52
54
|
this.requestTime = 0;
|
|
53
55
|
this.lastTime = now;
|
|
54
56
|
this.lastUsage = currentCpu;
|
|
55
|
-
|
|
57
|
+
|
|
56
58
|
// Prune old data (keep 24 hours)
|
|
57
59
|
const cutoff = now - (24 * 60 * 60 * 1000); // 24 hours ago
|
|
58
60
|
this.history = this.history.filter(m => m.time > cutoff);
|
|
@@ -82,6 +84,7 @@ class ServerStats {
|
|
|
82
84
|
this.taskMap.set(name, info);
|
|
83
85
|
info.frequency = frequency;
|
|
84
86
|
info.state = "Started";
|
|
87
|
+
info.status = "started"
|
|
85
88
|
}
|
|
86
89
|
|
|
87
90
|
task(name, state) {
|
|
@@ -89,6 +92,25 @@ class ServerStats {
|
|
|
89
92
|
if (info) {
|
|
90
93
|
info.date = Date.now();
|
|
91
94
|
info.state = state;
|
|
95
|
+
info.status = 'working';
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
taskDone(name, state) {
|
|
100
|
+
let info = this.taskMap.get(name);
|
|
101
|
+
if (info) {
|
|
102
|
+
info.date = Date.now();
|
|
103
|
+
info.state = state;
|
|
104
|
+
info.status = 'resting';
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
taskError(name, state) {
|
|
109
|
+
let info = this.taskMap.get(name);
|
|
110
|
+
if (info) {
|
|
111
|
+
info.date = Date.now();
|
|
112
|
+
info.state = state;
|
|
113
|
+
info.status = 'error';
|
|
92
114
|
}
|
|
93
115
|
}
|
|
94
116
|
|
|
@@ -96,17 +118,19 @@ class ServerStats {
|
|
|
96
118
|
if (this.taskMap.size == 0) {
|
|
97
119
|
return "";
|
|
98
120
|
}
|
|
99
|
-
let html = '<table class="grid"
|
|
100
|
-
html += "<tr><th>Task</th><th>Status</th><th>Frequency</th><th>Last Seen</th></tr>";
|
|
121
|
+
let html = '<table class="grid">';
|
|
122
|
+
html += "<tr><th>Background Task</th><th>Status</th><th>Frequency</th><th>Last Seen</th></tr>";
|
|
101
123
|
for (let m of this.taskMap.keys()) {
|
|
102
|
-
|
|
124
|
+
let mm = this.taskMap.get(m);
|
|
125
|
+
let color = this.getTaskColor(mm.status);
|
|
126
|
+
html += `<tr style="background-color: ${color}"><td>`;
|
|
103
127
|
html += escape(m);
|
|
104
128
|
html += "</td><td>";
|
|
105
|
-
html += escape(
|
|
129
|
+
html += escape(mm.state);
|
|
106
130
|
html += "</td><td>";
|
|
107
|
-
html +=
|
|
131
|
+
html += mm.frequency;
|
|
108
132
|
html += "</td><td>";
|
|
109
|
-
html += Utilities.formatDuration(
|
|
133
|
+
html += Utilities.formatDuration(mm.date, Date.now());
|
|
110
134
|
html += "</td></tr>";
|
|
111
135
|
}
|
|
112
136
|
html += "</table>";
|
|
@@ -128,5 +152,14 @@ class ServerStats {
|
|
|
128
152
|
return { idle, total };
|
|
129
153
|
}
|
|
130
154
|
|
|
155
|
+
getTaskColor(status) {
|
|
156
|
+
switch (status) {
|
|
157
|
+
case "started": return "LightGrey";
|
|
158
|
+
case "working": return "LightGreen";
|
|
159
|
+
case "resting": return "White";
|
|
160
|
+
case "error": return "LightRed";
|
|
161
|
+
default: return "DarkBlue"; // should not happen
|
|
162
|
+
}
|
|
163
|
+
}
|
|
131
164
|
}
|
|
132
165
|
module.exports = ServerStats;
|
package/tx/README.md
CHANGED
|
@@ -201,7 +201,7 @@ Loads LOINC from a SQLite database file.
|
|
|
201
201
|
```
|
|
202
202
|
|
|
203
203
|
The filename is downloaded from the base URL if not cached. Database files must be in the server's proprietary format.
|
|
204
|
-
The file is built by importing LOINC (
|
|
204
|
+
The file is built by importing LOINC (see [documentation](importers/readme.md))
|
|
205
205
|
|
|
206
206
|
#### `rxnorm` - RxNorm
|
|
207
207
|
|
|
@@ -211,7 +211,7 @@ Loads RxNorm drug terminology from a SQLite database file.
|
|
|
211
211
|
- rxnorm:rxnorm_02032025-a.db
|
|
212
212
|
```
|
|
213
213
|
|
|
214
|
-
The file is built by importing RxNorm (
|
|
214
|
+
The file is built by importing RxNorm (see [documentation](importers/readme.md))
|
|
215
215
|
|
|
216
216
|
#### `ndc` - NDC (National Drug Code)
|
|
217
217
|
|
|
@@ -228,7 +228,7 @@ Loads FDA UNII codes from a SQLite database file.
|
|
|
228
228
|
```yaml
|
|
229
229
|
- unii:unii_20240622.db
|
|
230
230
|
```
|
|
231
|
-
The file is built by importing UNII (
|
|
231
|
+
The file is built by importing UNII (see [documentation](importers/readme.md))
|
|
232
232
|
|
|
233
233
|
#### `snomed` - SNOMED CT
|
|
234
234
|
|
|
@@ -254,7 +254,7 @@ Common edition identifiers:
|
|
|
254
254
|
- `nl` - Netherlands
|
|
255
255
|
- `ips` - IPS (International Patient Summary) Free Set
|
|
256
256
|
|
|
257
|
-
The file is built by importing SNOMED CT (
|
|
257
|
+
The file is built by importing SNOMED CT (see [documentation](importers/readme.md))
|
|
258
258
|
|
|
259
259
|
|
|
260
260
|
#### `cpt` - CPT (Current Procedural Terminology)
|
|
@@ -267,7 +267,7 @@ Loads CPT codes from a SQLite database file.
|
|
|
267
267
|
|
|
268
268
|
**Note:** CPT is copyrighted by the American Medical Association. Ensure you have appropriate licensing.
|
|
269
269
|
|
|
270
|
-
The file is built by importing CPT (
|
|
270
|
+
The file is built by importing CPT (see [documentation](importers/readme.md))
|
|
271
271
|
|
|
272
272
|
#### `omop` - OMOP Vocabularies
|
|
273
273
|
|
|
@@ -276,7 +276,7 @@ Loads OMOP (Observational Medical Outcomes Partnership) vocabulary mappings from
|
|
|
276
276
|
```yaml
|
|
277
277
|
- omop:omop_v20250227.db
|
|
278
278
|
```
|
|
279
|
-
The file is built by importing OMOP (
|
|
279
|
+
The file is built by importing OMOP (see [documentation](importers/readme.md))
|
|
280
280
|
|
|
281
281
|
#### `npm` - FHIR NPM Packages
|
|
282
282
|
|
package/tx/cs/cs-api.js
CHANGED
|
@@ -9,6 +9,9 @@ const {validateParameter, validateArrayParameter} = require("../../library/utili
|
|
|
9
9
|
const {I18nSupport} = require("../../library/i18nsupport");
|
|
10
10
|
const {VersionUtilities} = require("../../library/version-utilities");
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* For documentation, see cs-api.md
|
|
14
|
+
*/
|
|
12
15
|
class FilterExecutionContext {
|
|
13
16
|
filters = [];
|
|
14
17
|
forIterate = false;
|