Compare commits

...

11 Commits

10 changed files with 157 additions and 108 deletions

View File

@ -3,12 +3,14 @@
<Navbar v-model:server="server" :user="user" /> <Navbar v-model:server="server" :user="user" />
<div class="container"> <div class="container">
<Login :secret="secret" v-model:client="client" v-model:server="server" v-model:user="user" /> <Login :secret="secret" v-model:client="client" v-model:server="server" v-model:user="user" />
<router-view v-if="user"></router-view> <router-view v-if="user" :client="client"></router-view>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import { watchEffect } from 'vue';
import Navbar from './Navbar.vue'; import Navbar from './Navbar.vue';
import Login from './Login.vue'; import Login from './Login.vue';
import RouteSchedule from './RouteSchedule.vue'; import RouteSchedule from './RouteSchedule.vue';
@ -38,15 +40,17 @@
if(this.heartbeat) window.clearInterval(this.heartbeat); if(this.heartbeat) window.clearInterval(this.heartbeat);
else { else {
[ [
{ path: '/', component: RouteSchedule, props: { client: this.client } }, { path: '/', component: RouteSchedule },
{ path: '/patient', component: RoutePatientLookup, props: { client: this.client } }, { path: '/patient', component: RoutePatientLookup },
{ path: '/patient/:id', component: RoutePatientDetail, props: { client: this.client } }, { path: '/patient/:id', component: RoutePatientDetail },
{ path: '/recall', component: RouteRecall, props: { client: this.client } }, { path: '/recall', component: RouteRecall },
].forEach(route => this.$root.$router.addRoute(route)); ].forEach(route => this.$root.$router.addRoute(route));
await this.$root.$router.replace(this.$route); await this.$root.$router.replace(this.$route);
} }
if(value) this.heartbeat = await value.heartbeat(); if(value) {
if(oldvalue) this.$router.go(); // refresh if changed this.heartbeat = await value.heartbeat();
var stop = watchEffect(() => { if(!value.connected.value) { stop(); this.client = this.server = this.user = null; } });
}
} }
} }
}; };

View File

@ -1,7 +1,7 @@
<template> <template>
<div class="accordion mb-3 shadow"> <div class="accordion mb-3 shadow">
<div class="accordion-item"> <div class="accordion-item">
<h2 class="accordion-header"><button class="accordion-button" :class="{ testing: (x_server) && (x_server.production != '1') }" type="button" @click="() => show = !show"><template v-if="user">{{user[2]}}<template v-if="server"> @ {{server.domain}}</template></template><template v-else>Login</template></button></h2> <h2 class="accordion-header"><button class="accordion-button" :class="{ testing: (x_server) && (x_server.production) && (x_server.production != '1') }" type="button" @click="() => show = !show"><template v-if="user">{{user[2]}}<template v-if="server"> @ {{server.domain}}</template></template><template v-else>Login</template></button></h2>
<div class="accordion-collapse collapse" :class="{ show }"> <div class="accordion-collapse collapse" :class="{ show }">
<div class="accordion-body"> <div class="accordion-body">
<div class="card"> <div class="card">
@ -44,7 +44,6 @@
</style> </style>
<script> <script>
import cookie from './cookie.mjs';
import vistax from './vistax.mjs'; import vistax from './vistax.mjs';
export default { export default {
@ -74,7 +73,6 @@
data() { data() {
return { return {
show: false, show: false,
host: cookie.get('host'),
x_client: this.client, x_client: this.client,
x_server: this.server, x_server: this.server,
x_user: this.user, x_user: this.user,
@ -83,25 +81,33 @@
verifycode: null verifycode: null
}; };
}, },
computed: {
host: {
get() {
return vistax.state.host;
},
set(value) {
vistax.state.host = value;
}
}
},
watch: { watch: {
host(value) { host: {
cookie.set('host', value); handler(value) {
this.logout(); this.connect();
}, immediate: true
}, },
client(value) { this.x_client = value; }, client(value) { this.x_client = value; },
x_client(value) { this.$emit('update:client', value); }, x_client(value) { this.$emit('update:client', value); if(!value) this.connect(); },
server(value) { this.x_server = value; }, server(value) { this.x_server = value; },
x_server(value) { this.$emit('update:server', value); }, x_server(value) { this.$emit('update:server', value); },
user(value) { this.x_user = value; }, user(value) { this.x_user = value; },
x_user(value) { this.$emit('update:user', value); } x_user(value) { this.$emit('update:user', value); }
}, },
async mounted() {
this.connect();
},
methods: { methods: {
async connect() { async connect() {
if(this.x_client) return this.x_client; this.logout();
this.x_client = await vistax.Client.fromCookie(this.secret); this.x_client = await (this.host ? vistax.Client.fromCookie(this.secret, this.host) : vistax.Client.fromCookie(this.secret));
this.banner = await this.x_client.XUS_INTRO_MSG(); this.banner = await this.x_client.XUS_INTRO_MSG();
if((await this.x_client.userinfo()).result) try { if((await this.x_client.userinfo()).result) try {
var user = await this.x_client.XUS_GET_USER_INFO(); var user = await this.x_client.XUS_GET_USER_INFO();
@ -114,19 +120,19 @@
this.$emit('update:server', this.x_server = (await this.x_client.serverinfo()).result); this.$emit('update:server', this.x_server = (await this.x_client.serverinfo()).result);
console.log('Backend secret', this.secret); console.log('Backend secret', this.secret);
console.log(this.banner); console.log(this.banner);
return this.x_client;
}, },
async login(evt) { async login(evt) {
if(!this.x_client) await this.connect(); if(this.x_client) {
var res = await ((this.accesscode && this.verifycode) ? this.x_client.authenticate(this.accesscode + ';' + this.verifycode) : this.x_client.authenticate()); var res = await ((this.accesscode && this.verifycode) ? this.x_client.authenticate(this.accesscode + ';' + this.verifycode) : this.x_client.authenticate());
if(!!res.result[0]) { if(!!res.result[0]) {
var user = await this.x_client.XUS_GET_USER_INFO(); var user = await this.x_client.XUS_GET_USER_INFO();
this.x_user = user[0] ? user : null this.x_user = user[0] ? user : null
} else this.x_user = null; } else this.x_user = null;
this.$emit('update:user', this.x_user); this.$emit('update:user', this.x_user);
this.show = !this.x_user; this.show = !this.x_user;
this.$emit('update:server', this.x_server = (await this.x_client.serverinfo()).result); this.$emit('update:server', this.x_server = (await this.x_client.serverinfo()).result);
console.log('Authenticate', res); console.log('Authenticate', res);
}
}, },
async logout(evt) { async logout(evt) {
if(this.x_client) { if(this.x_client) {
@ -135,6 +141,8 @@
this.$emit('update:server', this.x_server = null); this.$emit('update:server', this.x_server = null);
this.$emit('update:user', this.x_user = null); this.$emit('update:user', this.x_user = null);
} }
this.banner = null;
this.show = true;
} }
} }
}; };

View File

@ -65,7 +65,7 @@
var name = this.$route.query.name.toUpperCase(); var name = this.$route.query.name.toUpperCase();
var patient = await this.client.ORWPT_LAST5(id); var patient = await this.client.ORWPT_LAST5(id);
for(var i = 0; i < patient.length; ++i) if(name == patient[i].name) { for(var i = 0; i < patient.length; ++i) if(name == patient[i].name) {
this.$router.replace('/patient/' + patient[0].dfn); this.$router.replace('/patient/' + patient[i].dfn);
break; break;
} }
} }

View File

@ -45,7 +45,7 @@
</template> </template>
<script> <script>
import cookie from './cookie.mjs'; import { state } from './vistax.mjs';
import { groupByArray, strtr_unscramble, strHashHSL, strftime_vista, debounce } from './util.mjs'; import { groupByArray, strtr_unscramble, strHashHSL, strftime_vista, debounce } from './util.mjs';
import ViewResourceLookup from './ViewResourceLookup.vue'; import ViewResourceLookup from './ViewResourceLookup.vue';
@ -62,7 +62,7 @@
client: Object client: Object
}, },
data() { data() {
var resources = cookie.get('vista.resources'); var resources = state.resources;
var today = dateonly(new Date()); var today = dateonly(new Date());
return { return {
selection: resources ? (resources.split(',').filter(x => x) || []) : [], selection: resources ? (resources.split(',').filter(x => x) || []) : [],
@ -95,7 +95,7 @@
}, },
created() { created() {
this.debounced_selection = debounce(async function(value) { this.debounced_selection = debounce(async function(value) {
cookie.set('vista.resources', value.join(','), 7); state.resources = value.join(',');
var patients = this.selection.length > 0 ? groupByArray(await this.client.SDEC_CLINLET(this.selection.join('|') + '|', strftime_vista(this.date_begin), strftime_vista(this.date_end)), x => x.HRN) : [], now = new Date(), group, values, appt; var patients = this.selection.length > 0 ? groupByArray(await this.client.SDEC_CLINLET(this.selection.join('|') + '|', strftime_vista(this.date_begin), strftime_vista(this.date_end)), x => x.HRN) : [], now = new Date(), group, values, appt;
for(var i = patients.length - 1; i >= 0; --i) { for(var i = patients.length - 1; i >= 0; --i) {
group = patients[i]; group = patients[i];

View File

@ -19,7 +19,7 @@
</template> </template>
<script> <script>
import cookie from './cookie.mjs'; import { state } from './vistax.mjs';
import ViewResourceLookup from './ViewResourceLookup.vue'; import ViewResourceLookup from './ViewResourceLookup.vue';
import DateRangePicker from './DateRangePicker.vue'; import DateRangePicker from './DateRangePicker.vue';
@ -37,7 +37,7 @@
client: Object client: Object
}, },
data() { data() {
var resources = cookie.get('vista.resources'); var resources = state.resources;
return { return {
selection: resources ? (resources.split(',').filter(x => x) || []) : [], selection: resources ? (resources.split(',').filter(x => x) || []) : [],
date: dateonly(new Date()), date: dateonly(new Date()),
@ -46,7 +46,7 @@
}, },
watch: { watch: {
selection(value, oldvalue) { selection(value, oldvalue) {
cookie.set('vista.resources', value.join(','), 7); state.resources = value.join(',');
} }
} }
}; };

View File

@ -1,29 +1,31 @@
<template> <template>
<div class="input-group"> <div>
<span class="input-group-text">🔎</span> <div class="input-group">
<input class="form-control" v-model="query_raw" @focus="collapsed = false" @blur="collapsed = true" /> <span class="input-group-text">🔎</span>
</div> <input class="form-control" v-model="query_raw" @focus="collapsed = false" />
<div :class="{ collapsed }" style="max-height: 30em; overflow-y: auto;"> </div>
<table class="table table-striped" style="font-family: monospace;" v-if="resultset_raw && resultset_raw.length > 0"> <div :class="{ collapsed }" style="max-height: 30em; overflow-y: auto;">
<thead> <table class="table table-striped" style="font-family: monospace;" v-if="resultset_raw && resultset_raw.length > 0">
<tr><th></th><th>ID</th><th>Name</th><th>Type</th><th>User</th></tr> <thead>
</thead> <tr><th></th><th>ID</th><th>Name</th><th>Type</th><th>User</th></tr>
<tbody> </thead>
<template v-for="row in resultset_filtered"> <tbody>
<tr :class="{ 'table-active': row.selected }" v-if="row.INACTIVE != 'YES'"> <template v-for="row in resultset_filtered">
<td><input type="checkbox" v-model="row.selected" /></td> <tr :class="{ 'table-active': row.selected }" v-if="row.INACTIVE != 'YES'">
<td>{{row.RESOURCEID}}</td> <td><input type="checkbox" v-model="row.selected" /></td>
<td>{{row.RESOURCE_NAME}}</td> <td>{{row.RESOURCEID}}</td>
<td>{{row.RESOURCETYPE}}</td> <td>{{row.RESOURCE_NAME}}</td>
<td>{{row.USERNAME}}</td> <td>{{row.RESOURCETYPE}}</td>
</tr> <td>{{row.USERNAME}}</td>
</template> </tr>
</tbody> </template>
</table> </tbody>
</div> </table>
<div style="font-family: monospace;" v-if="resultset_selected.length"> </div>
<span class="badge bg-primary" style="cursor: default; margin-right: 0.35em;" v-on:click="reset">CLEAR {{resultset_selected.length}}</span> <div style="font-family: monospace;" v-if="resultset_selected.length">
<span class="badge bg-secondary" style="cursor: default; margin-right: 0.35em;" v-for="row in resultset_selected" v-on:click="row.selected = false;"> {{row.RESOURCEID}} {{row.RESOURCE_NAME}}</span> <span class="badge bg-primary" style="cursor: default; margin-right: 0.35em;" v-on:click="reset">CLEAR {{resultset_selected.length}}</span>
<span class="badge bg-secondary" style="cursor: default; margin-right: 0.35em;" v-for="row in resultset_selected" v-on:click="row.selected = false;"> {{row.RESOURCEID}} {{row.RESOURCE_NAME}}</span>
</div>
</div> </div>
</template> </template>
@ -91,6 +93,7 @@
} }
}, },
methods: { methods: {
collapse(evt) { if(!this.$el.contains(evt.target)) this.collapsed = true; },
reset(evt) { reset(evt) {
var selection = this.resultset_selected.slice(); var selection = this.resultset_selected.slice();
for(var i = selection.length - 1; i >= 0; --i) selection[i].selected = false; for(var i = selection.length - 1; i >= 0; --i) selection[i].selected = false;
@ -103,6 +106,10 @@
var rs = (await this.client.SDEC_RESOURCE()).slice(); var rs = (await this.client.SDEC_RESOURCE()).slice();
update_selection(rs, this.selection); update_selection(rs, this.selection);
this.resultset_raw = rs; this.resultset_raw = rs;
document.addEventListener('click', this.collapse);
},
destroyed() {
document.removeEventListener('click', this.collapse);
} }
}; };
</script> </script>

View File

@ -17,7 +17,7 @@
</template> </template>
<script> <script>
import cookie from './cookie.mjs'; import { state } from './vistax.mjs';
import { uniq, strtr_unscramble, strHashHSL, strfdate_vista, debounce } from './util.mjs'; import { uniq, strtr_unscramble, strHashHSL, strfdate_vista, debounce } from './util.mjs';
import Autocomplete from './Autocomplete.vue'; import Autocomplete from './Autocomplete.vue';
@ -60,15 +60,15 @@
strtr_unscramble, strtr_unscramble,
set_practitioner(patient, practitioner) { set_practitioner(patient, practitioner) {
this.practitioner[patient] = practitioner; this.practitioner[patient] = practitioner;
cookie.set('vista.practitioner', JSON.stringify(this.practitioner), 1); state.practitioner = this.practitioner;
} }
}, },
created() { created() {
this.debounced_params = debounce(async function(value) { this.appointments = value.selection.length > 0 ? (await this.client.SDEC_CLINLET(value.selection.join('|') + '|', strfdate_vista(value.date_begin), strfdate_vista(value.date_end))).sort((a, b) => (new Date(a.ApptDate)) - (new Date(b.ApptDate))) : []; }, 500); this.debounced_params = debounce(async function(value) { this.appointments = value.selection.length > 0 ? (await this.client.SDEC_CLINLET(value.selection.join('|') + '|', strfdate_vista(value.date_begin), strfdate_vista(value.date_end))).sort((a, b) => (new Date(a.ApptDate)) - (new Date(b.ApptDate))) : []; }, 500);
}, },
async mounted() { async mounted() {
var practitioner = cookie.get('vista.practitioner'); var practitioner = state.practitioner;
if(practitioner) this.practitioner = JSON.parse(practitioner); if(practitioner) this.practitioner = practitioner;
this.production = (await this.client.serverinfo()).result.production == '1'; this.production = (await this.client.serverinfo()).result.production == '1';
} }
}; };

View File

@ -33,7 +33,8 @@
{ name: 'Electrolytes', value: ['SODIUM', 'CHLORIDE', 'CO2', 'CALCIUM', 'IONIZED CALCIUM (LABCORP)', 'POTASSIUM', 'MAGNESIUM', 'PO4', 'ANION GAP', 'OSMOBLD'], selected: false }, { name: 'Electrolytes', value: ['SODIUM', 'CHLORIDE', 'CO2', 'CALCIUM', 'IONIZED CALCIUM (LABCORP)', 'POTASSIUM', 'MAGNESIUM', 'PO4', 'ANION GAP', 'OSMOBLD'], selected: false },
{ name: 'Coagulation', value: ['PT', 'INR', 'PTT'], selected: false }, { name: 'Coagulation', value: ['PT', 'INR', 'PTT'], selected: false },
{ name: 'Vitamins', value: ['FERRITIN', 'IRON', 'TIBC', 'B 12', 'FOLATE', 'VITAMIN D TOTAL 25-OH'], selected: false }, { name: 'Vitamins', value: ['FERRITIN', 'IRON', 'TIBC', 'B 12', 'FOLATE', 'VITAMIN D TOTAL 25-OH'], selected: false },
{ name: 'Thyroid', value: ['TSH', 'T4 (THYROXINE)'], selected: false } { name: 'Thyroid', value: ['TSH', 'T4 (THYROXINE)'], selected: false },
{ name: 'Myeloma', value: ['PROTEIN,TOT SER (LC)', 'ALBUMIN [for SPEP](LC)', 'ALPHA-1 GLOBULIN S (LC)', 'ALPHA-2 GLOBULIN S (LC)', 'BETA GLOBULIN S (LC)', 'GAMMA GLOBULIN S (LC)', 'GLOBULIN,TOTAL S (LC)', 'A/G RATIO S (LC)', 'M-SPIKE S (LC)', 'IMMUNOFIXATION SERUM (LC)', 'FREE KAPPA LT CHAIN, S (LC)', 'FREE LAMBDA LT CHAIN, S (LC)', 'KAPPA/LAMBDA RATIO, S (LC)', 'KLRATIO', 'IMMUNOGLOBULIN G,QN (LC)', 'IMMUNOGLOBULIN A,QN (LC)', 'IMMUNOGLOBULIN M,QN (LC)', 'IGG', 'IGA', 'IGM', 'ALBUMIN [for RAND UR](LC)', 'ALPHA-1 GLOB RAND UR(LC)', 'ALPHA-2 GLOB RAND UR(LC)', 'BETA GLOB RAND UR(LC)', 'GAMMA GLOB RAND UR(LC)', 'M-SPIKE% RAND UR(LC)', 'PROTEIN,TOT UR(LC)', 'FKLCUR', 'FLLCUR', 'KAPPA/LAMBDA RATIO, UR (LC)', 'PROTEIN,24H CALC(LC)', 'ALBUMIN [for 24UPEP](LC)', 'ALPHA-1 GLOBULIN 24H(LC)', 'ALPHA-2 GLOBULIN 24H(LC)', 'BETA GLOBULIN 24H(LC)', 'GAMMA GLOBULIN 24H(LC)', 'M-SPIKE mg/24hr(LC)', 'FR KAPPA LTCH', 'FR LAMBDA LTCH'], selected: false }
]; ];
reports.reduce((acc, x) => acc[x] = x, reports); reports.reduce((acc, x) => acc[x] = x, reports);
@ -60,9 +61,10 @@
function labs_normalize(rs) { function labs_normalize(rs) {
return rs.map(function(x) { return rs.map(function(x) {
var specimen = x.specimen;
return { return {
time: x.time_collected, time: x.time_collected,
name: x.name, name: (specimen == 'BLOOD') || (specimen == 'SERUM') || (specimen == 'PLASMA') ? x.name : (specimen == 'RANDOM URINE') ? x.name + ':UR' : specimen ? x.name + ':' + specimen.charAt(0) : x.name,
unit: x.unit, unit: x.unit,
range: x.range, range: x.range,
value: x.value, value: x.value,

View File

@ -52,13 +52,15 @@ function lab_parse1default(data) {
if(x.comment) x.comment.push(line.substring(12)); if(x.comment) x.comment.push(line.substring(12));
else x.comment = [line.substring(12)]; else x.comment = [line.substring(12)];
} else console.log('DANGLING:', line); } else console.log('DANGLING:', line);
} else if((line.startsWith('COVID-19 SCR (CEPHEID-RAPID)')) && (m = line.substring(28).match(/^(?<value>.*?)(?: (?<flag>L\*|L|H\*|H))?\s+(?:(?<unit>.{10}) (?<range>.{16}) \[(?<site>\d+)\])?$/))) { } else if(m = line.match(/^\b(?<name>.*?)\s{2,}(?<value>.*?)(?: (?<flag>L\*|L|H\*|H))?\s+(?:(?<unit>.{10}) (?<range>.*?)(?: \[(?<site>\d+)\])?)?$/)) {
if((m.groups.range) && (m.groups.range.startsWith('Ref: '))) m.groups.range = m.groups.range.substring(5); if((m.groups.range) && (m.groups.range.startsWith('Ref: '))) m.groups.range = m.groups.range.substring(5);
results.push(x = m.groups); results.push(x = m.groups);
if((x.value === '') && (m = x.name.match(/^(?<name>.*?)(?<value>(?:[\d\.]+|positive|negative|reactive|not detected|collected - specimen in lab|test not performed))\s*$/i))) {
x.name = m.groups.name;
x.value = m.groups.value;
}
for(var k in x) if(x[k]) x[k] = x[k] ? x[k].replace(/^\s+|\s+$/g, '') : undefined; for(var k in x) if(x[k]) x[k] = x[k] ? x[k].replace(/^\s+|\s+$/g, '') : undefined;
x.name = 'COVID-19 SCR (CEPHEID-RAPID)'; } else if(m = line.match(/^\b(?<name>.*?) (?<value>(?:[\d\.]+|positive|negative|reactive|not detected|collected - specimen in lab|test not performed))\s*$/i)) {
} else if(m = line.match(/^\b(?<name>.*?)\s{2,}(?<value>.*?)(?: (?<flag>L\*|L|H\*|H))?\s+(?:(?<unit>.{10}) (?<range>.{16}) \[(?<site>\d+)\])?$/)) {
if((m.groups.range) && (m.groups.range.startsWith('Ref: '))) m.groups.range = m.groups.range.substring(5);
results.push(x = m.groups); results.push(x = m.groups);
for(var k in x) if(x[k]) x[k] = x[k] ? x[k].replace(/^\s+|\s+$/g, '') : undefined; for(var k in x) if(x[k]) x[k] = x[k] ? x[k].replace(/^\s+|\s+$/g, '') : undefined;
} else if(line.startsWith(' [')) { } else if(line.startsWith(' [')) {
@ -72,6 +74,13 @@ function lab_parse1default(data) {
x.site = line.split('[')[1].split(']')[0]; x.site = line.split('[')[1].split(']')[0];
} else x.range = line.replace(/^\s+|\s+$/g, ''); } else x.range = line.replace(/^\s+|\s+$/g, '');
} else console.log('DANGLING:', line); } else console.log('DANGLING:', line);
} else if(m = line.match(/^\s{40}\b(?:(?<unit>.{10}) (?<range>.*?)(?: \[(?<site>\d+)\])?)?$/)) {
if(results.length > 0) {
x = results[results.length - 1];
if(m.groups.unit) x.unit = m.groups.unit.replace(/^\s+|\s+$/g, '');
if(m.groups.range) x.range = m.groups.range.replace(/^\s+|\s+$/g, '');
if(m.groups.site) x.site = m.groups.site.replace(/^\s+|\s+$/g, '');
} else console.log('DANGLING:', line, m.groups);
} else console.log('INVALID:', line); } else console.log('INVALID:', line);
} }
for(var i = results.length - 1; i >= 0; --i) { for(var i = results.length - 1; i >= 0; --i) {

View File

@ -1,8 +1,16 @@
import { reactive, watch } from 'vue';
import vista from './vista.mjs'; import vista from './vista.mjs';
import cookie from './cookie.mjs'; import cookie from './cookie.mjs';
import { lab_parse, lab_reparse_results, measurement_parse } from './reportparser.mjs'; import { lab_parse, lab_reparse_results, measurement_parse } from './reportparser.mjs';
const COOKIE_TIME = 45; export const state = reactive(cookie.get('state') ? JSON.parse(cookie.get('state')) : {});
if((!state.secret) && (cookie.get('secret'))) state.resources = cookie.get('secret');
if((!state.cid) && (cookie.get('cid'))) state.resources = cookie.get('cid');
if((!state.host) && (cookie.get('host'))) state.resources = cookie.get('host');
if((!state.resources) && (cookie.get('vista.resources'))) state.resources = cookie.get('vista.resources');
if((!state.practitioner) && (cookie.get('vista.practitioner'))) state.practitioner = JSON.parse(cookie.get('vista.practitioner'));
watch(state, value => cookie.set('state', JSON.stringify(value), 45), { immediate: true, deep: true });
function RPCError(type, ...args) { function RPCError(type, ...args) {
this.name = type; this.name = type;
@ -76,13 +84,24 @@ export function Client(cid, secret) {
this.secret = secret; this.secret = secret;
this.cid = cid; this.cid = cid;
this.connected = reactive({ value: true });
this.close = function() { this.close = function() {
console.log('CLOSE', cid);
if(heartbeat) window.clearInterval(heartbeat); if(heartbeat) window.clearInterval(heartbeat);
this.connected.value = false;
return vista.close(cid); return vista.close(cid);
}; };
this.call = (method, ...params) => vista.call(cid, method, ...params); this.call = async function(method, ...params) {
this.callctx = (context, method, ...params) => vista.callctx(cid, context, method, ...params); var res = await vista.call(cid, method, ...params);
if((res.error) && (res.error.type == 'ConnectionResetError')) this.close();
return res;
};
this.callctx = async function(context, method, ...params) {
var res = vista.callctx(cid, context, method, ...params);
if((res.error) && (res.error.type == 'ConnectionResetError')) this.close();
return res;
};
this.heartbeat = async function(interval=null) { this.heartbeat = async function(interval=null) {
if(!interval) interval = 0.45*1000*(await this.XWB_GET_BROKER_INFO())[0]; if(!interval) interval = 0.45*1000*(await this.XWB_GET_BROKER_INFO())[0];
if(heartbeat) window.clearInterval(heartbeat); if(heartbeat) window.clearInterval(heartbeat);
@ -93,27 +112,27 @@ export function Client(cid, secret) {
this.userinfo = () => vista.userinfo(cid); this.userinfo = () => vista.userinfo(cid);
this.authenticate = (avcode=null) => vista.authenticate(cid, avcode); this.authenticate = (avcode=null) => vista.authenticate(cid, avcode);
this.XWB_IM_HERE = unwrapped(logged(() => vista.call(cid, 'XWB_IM_HERE'), 'XWB_IM_HERE')); this.XWB_IM_HERE = unwrapped(logged(() => this.call('XWB_IM_HERE'), 'XWB_IM_HERE'));
this.XUS_INTRO_MSG = memoized(unwrapped(logged(() => vista.callctx(cid, ['XUCOMMAND'], 'XUS_INTRO_MSG'), 'XUS_INTRO_MSG'))); this.XUS_INTRO_MSG = memoized(unwrapped(logged(() => this.callctx(['XUCOMMAND'], 'XUS_INTRO_MSG'), 'XUS_INTRO_MSG')));
this.XWB_GET_BROKER_INFO = memoized(unwrapped(logged(() => vista.callctx(cid, ['XUCOMMAND'], 'XWB_GET_BROKER_INFO'), 'XWB_GET_BROKER_INFO'))); this.XWB_GET_BROKER_INFO = memoized(unwrapped(logged(() => this.callctx(['XUCOMMAND'], 'XWB_GET_BROKER_INFO'), 'XWB_GET_BROKER_INFO')));
this.XUS_GET_USER_INFO = memoized(unwrapped(logged(() => vista.call(cid, 'XUS_GET_USER_INFO'), 'XUS_GET_USER_INFO'))); this.XUS_GET_USER_INFO = memoized(unwrapped(logged(() => this.call('XUS_GET_USER_INFO'), 'XUS_GET_USER_INFO')));
this.SDEC_RESOURCE = memoized(unwrapped(logged(() => vista.callctx(cid, ['SDECRPC'], 'SDEC_RESOURCE'), 'SDEC_RESOURCE'))); this.SDEC_RESOURCE = memoized(unwrapped(logged(() => this.callctx(['SDECRPC'], 'SDEC_RESOURCE'), 'SDEC_RESOURCE')));
this.SDEC_CLINLET = memoized(unwrapped(logged((...args) => vista.callctx(cid, ['SDECRPC'], 'SDEC_CLINLET', ...args), 'SDEC_CLINLET'))); this.SDEC_CLINLET = memoized(unwrapped(logged((...args) => this.callctx(['SDECRPC'], 'SDEC_CLINLET', ...args), 'SDEC_CLINLET')));
this.ORWPT_FULLSSN = memoized(caretseparated(unwrapped(logged((...args) => vista.callctx(cid, ['OR CPRS GUI CHART'], 'ORWPT_FULLSSN', ...args), 'ORWPT_FULLSSN')), ['dfn', 'name', 'date', 'pid'])); this.ORWPT_FULLSSN = memoized(caretseparated(unwrapped(logged((...args) => this.callctx(['OR CPRS GUI CHART'], 'ORWPT_FULLSSN', ...args), 'ORWPT_FULLSSN')), ['dfn', 'name', 'date', 'pid']));
this.ORWPT_LAST5 = memoized(caretseparated(unwrapped(logged((...args) => vista.callctx(cid, ['OR CPRS GUI CHART'], 'ORWPT_LAST5', ...args), 'ORWPT_LAST5')), ['dfn', 'name', 'date', 'pid'])); this.ORWPT_LAST5 = memoized(caretseparated(unwrapped(logged((...args) => this.callctx(['OR CPRS GUI CHART'], 'ORWPT_LAST5', ...args), 'ORWPT_LAST5')), ['dfn', 'name', 'date', 'pid']));
this.ORWPT_ID_INFO = memoized(caretseparated1(unwrapped(logged((...args) => vista.callctx(cid, ['OR CPRS GUI CHART'], 'ORWPT_ID_INFO', ...args), 'ORWPT_ID_INFO')), ['pid', 'dob', 'sex', 'vet', 'sc_percentage', 'ward', 'room_bed', 'name'])); this.ORWPT_ID_INFO = memoized(caretseparated1(unwrapped(logged((...args) => this.callctx(['OR CPRS GUI CHART'], 'ORWPT_ID_INFO', ...args), 'ORWPT_ID_INFO')), ['pid', 'dob', 'sex', 'vet', 'sc_percentage', 'ward', 'room_bed', 'name']));
this.ORWPT16_LOOKUP = memoized(caretseparated(unwrapped(logged((...args) => vista.callctx(cid, ['OR CPRS GUI CHART'], 'ORWPT16_LOOKUP', ...args), 'ORWPT16_LOOKUP')), ['dfn', 'name', 'pid'])); this.ORWPT16_LOOKUP = memoized(caretseparated(unwrapped(logged((...args) => this.callctx(['OR CPRS GUI CHART'], 'ORWPT16_LOOKUP', ...args), 'ORWPT16_LOOKUP')), ['dfn', 'name', 'pid']));
this.ORWPT16_ID_INFO = memoized(caretseparated1(unwrapped(logged((...args) => vista.callctx(cid, ['OR CPRS GUI CHART'], 'ORWPT16_ID_INFO', ...args), 'ORWPT16_ID_INFO')), ['pid', 'dob', 'age', 'sex', 'sc_percentage', 'type', 'ward', 'room_bed', 'name'])); this.ORWPT16_ID_INFO = memoized(caretseparated1(unwrapped(logged((...args) => this.callctx(['OR CPRS GUI CHART'], 'ORWPT16_ID_INFO', ...args), 'ORWPT16_ID_INFO')), ['pid', 'dob', 'age', 'sex', 'sc_percentage', 'type', 'ward', 'room_bed', 'name']));
this.ORQQVI_VITALS = memoized(caretseparated(unwrapped(logged((...args) => vista.callctx(cid, ['OR CPRS GUI CHART'], 'ORQQVI_VITALS', ...args), 'ORQQVI_VITALS')), ['measurement_ien', 'type', 'value', 'datetime', 'value_american', 'value_metric'])); this.ORQQVI_VITALS = memoized(caretseparated(unwrapped(logged((...args) => this.callctx(['OR CPRS GUI CHART'], 'ORQQVI_VITALS', ...args), 'ORQQVI_VITALS')), ['measurement_ien', 'type', 'value', 'datetime', 'value_american', 'value_metric']));
this.ORQQVI_VITALS_FOR_DATE_RANGE = memoized(caretseparated(unwrapped(logged((...args) => vista.callctx(cid, ['OR CPRS GUI CHART'], 'ORQQVI_VITALS_FOR_DATE_RANGE', ...args), 'ORQQVI_VITALS_FOR_DATE_RANGE')), ['measurement_ien', 'type', 'value', 'datetime'])); this.ORQQVI_VITALS_FOR_DATE_RANGE = memoized(caretseparated(unwrapped(logged((...args) => this.callctx(['OR CPRS GUI CHART'], 'ORQQVI_VITALS_FOR_DATE_RANGE', ...args), 'ORQQVI_VITALS_FOR_DATE_RANGE')), ['measurement_ien', 'type', 'value', 'datetime']));
this.GMV_EXTRACT_REC = memoized(async (dfn, oredt, orsdt) => measurement_parse(await unwrapped(logged((...args0) => vista.callctx(cid, ['OR CPRS GUI CHART'], 'GMV_EXTRACT_REC', args0.join('^')), 'GMV_EXTRACT_REC'))(dfn, oredt, '', orsdt))); this.GMV_EXTRACT_REC = memoized(async (dfn, oredt, orsdt) => measurement_parse(await unwrapped(logged((...args0) => this.callctx(['OR CPRS GUI CHART'], 'GMV_EXTRACT_REC', args0.join('^')), 'GMV_EXTRACT_REC'))(dfn, oredt, '', orsdt)));
this.ORWLRR_INTERIM = memoized(labreportparsed(unwrapped(logged((...args) => vista.callctx(cid, ['OR CPRS GUI CHART'], 'ORWLRR_INTERIM', ...args), 'ORWLRR_INTERIM')))); this.ORWLRR_INTERIM = memoized(labreportparsed(unwrapped(logged((...args) => this.callctx(['OR CPRS GUI CHART'], 'ORWLRR_INTERIM', ...args), 'ORWLRR_INTERIM'))));
this.ORWLRR_INTERIM_RESULTS = memoized(async (...args) => lab_reparse_results(await this.ORWLRR_INTERIM(...args))); this.ORWLRR_INTERIM_RESULTS = memoized(async (...args) => lab_reparse_results(await this.ORWLRR_INTERIM(...args)));
return this; return this;
@ -131,57 +150,57 @@ Client.fromScratch = async function(secret, host='vista.northport.med.va.gov', p
}; };
Client.fromCookie = async function(secret, defaulthost='vista.northport.med.va.gov:19209') { Client.fromCookie = async function(secret, defaulthost='vista.northport.med.va.gov:19209') {
if(!secret) secret = cookie.get('secret'); if(!secret) secret = state.secret;
if(secret) { if(secret) {
var host = cookie.get('host'); var host = state.host;
host = (host || defaulthost).split(':'); host = (host || defaulthost).split(':');
if(secret != cookie.get('secret')) { if(secret != state.secret) {
console.log('Using new secret', secret); console.log('Using new secret', secret);
var client = await Client.fromScratch(secret, host[0], host[1]); var client = await Client.fromScratch(secret, host[0], host[1]);
if(client) { if(client) {
cookie.set('host', host.join(':'), COOKIE_TIME); state.host = host.join(':');
cookie.set('secret', secret); state.secret = secret;
cookie.set('cid', client.cid); state.cid = client.cid;
console.log('Established connection', client.cid); console.log('Established connection', client.cid);
return client; return client;
} else { } else {
cookie.reset('secret'); delete state.secret;
cookie.reset('cid'); delete state.cid;
console.log('Failed to connect'); console.log('Failed to connect');
return null; return null;
} }
} else if(!cookie.get('cid')) { } else if(!state.cid) {
console.log('Using saved secret', secret); console.log('Using saved secret', secret);
var client = await Client.fromScratch(secret, host[0], host[1]); var client = await Client.fromScratch(secret, host[0], host[1]);
if(client) { if(client) {
cookie.set('host', host.join(':'), COOKIE_TIME); state.host = host.join(':');
cookie.set('secret', secret); state.secret = secret;
cookie.set('cid', client.cid); state.cid = client.cid;
console.log('Established connection', client.cid); console.log('Established connection', client.cid);
return client; return client;
} else { } else {
cookie.reset('secret'); delete state.secret;
cookie.reset('cid'); delete state.cid;
console.log('Failed connection'); console.log('Failed connection');
return null; return null;
} }
} else { } else {
console.log('Using saved secret and connection', secret); console.log('Using saved secret and connection', secret);
var cid = cookie.get('cid'); var cid = state.cid;
var client = Client.fromID(cid, secret); var client = Client.fromID(cid, secret);
if((await vista.call(cid, 'XWB_IM_HERE')).result == '1') { if((await vista.call(cid, 'XWB_IM_HERE')).result == '1') {
var server = await client.serverinfo(); var server = await client.serverinfo();
if((host[0] == server.result.host) && (host[1] == server.result.port)) { if((host[0] == server.result.host) && (host[1] == server.result.port)) {
cookie.set('host', host.join(':'), COOKIE_TIME); state.host = host.join(':');
return client; return client;
} else console.log('Rejecting previous connection to different server', server); } else console.log('Rejecting previous connection to different server', server);
} }
cookie.reset('cid'); delete state.cid;
return await Client.fromCookie(secret, host.join(':')); return await Client.fromCookie(secret, host.join(':'));
} }
} }
}; };
export default window.vistax = { export default window.vistax = {
RPCError, Client, connect: Client.fromCookie state, RPCError, Client, connect: Client.fromCookie
}; };