@bitpoolos/edge-bacnet 1.5.3 → 1.6.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 +143 -10
- package/bacnet_client.js +123 -107
- package/bacnet_gateway.html +22 -6
- package/bacnet_gateway.js +346 -250
- package/bacnet_inspector.html +43 -0
- package/bacnet_inspector.js +1564 -0
- package/bacnet_inspector_worker.js +535 -0
- package/bacnet_read.html +27 -27
- package/bacnet_read.js +0 -3
- package/common.js +201 -38
- package/inspector.html +460 -0
- package/package.json +6 -2
- package/resources/Logo_Simplified_Positive.svg +32 -0
- package/resources/downloadAsHtml.js +654 -0
- package/resources/icons/device-id-change-icon.svg +4 -0
- package/resources/icons/device-id-conflict-icon.svg +4 -0
- package/resources/icons/favicon.ico +0 -0
- package/resources/icons/points-error-icon.svg +4 -0
- package/resources/icons/points-missing-icon.svg +4 -0
- package/resources/icons/points-ok-icon.svg +4 -0
- package/resources/icons/points-unmapped-icon.svg +5 -0
- package/resources/icons/points-warning-icon.svg +4 -0
- package/resources/inspector.css +25312 -0
- package/resources/inspectorStyle.css +254 -0
- package/resources/inspectorStyles.css +478 -0
- package/resources/primevue.min.js +1 -0
- package/resources/style.css +17 -1
- package/resources/vue3513.global.prod.js +9 -0
- package/ssrHtmlExporter.js +535 -0
- package/treeBuilder.js +3 -3
|
@@ -0,0 +1,535 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exports PrimeVue apps as standalone HTML files
|
|
3
|
+
* Generates static HTML that can be viewed without a server
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Generate a standalone HTML file from a PrimeVue app using SSR
|
|
8
|
+
* @param {Object} appData - The application data to render
|
|
9
|
+
* @param {string} filename - The output filename
|
|
10
|
+
* @param {Object} options - Additional options for rendering
|
|
11
|
+
* @returns {Promise<string>} - The generated HTML content
|
|
12
|
+
*/
|
|
13
|
+
async function generatePrimeVueAppHtmlStatic(appData, filename = "bacnet-inspector-snapshot.html", options = {}) {
|
|
14
|
+
const {
|
|
15
|
+
title = "Bitpool BACnet Inspector",
|
|
16
|
+
logoBase64 = null,
|
|
17
|
+
customStyles = "",
|
|
18
|
+
} = options;
|
|
19
|
+
|
|
20
|
+
// Get embedded dependencies
|
|
21
|
+
const embeddedDependencies = await getEmbeddedDependencies();
|
|
22
|
+
const iconBase64Map = await getIconsAsBase64();
|
|
23
|
+
|
|
24
|
+
const fullHtml = `<!DOCTYPE html>
|
|
25
|
+
<html>
|
|
26
|
+
<head>
|
|
27
|
+
<meta charset="UTF-8">
|
|
28
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
29
|
+
<title>${title}</title>
|
|
30
|
+
|
|
31
|
+
<!-- Embedded styles -->
|
|
32
|
+
<style>
|
|
33
|
+
${embeddedDependencies.styles.theme || ''}
|
|
34
|
+
${embeddedDependencies.styles.primevue || ''}
|
|
35
|
+
${embeddedDependencies.styles.primeflex || ''}
|
|
36
|
+
${embeddedDependencies.styles.primeicons || ''}
|
|
37
|
+
${embeddedDependencies.styles.inspector || ''}
|
|
38
|
+
${customStyles}
|
|
39
|
+
</style>
|
|
40
|
+
</head>
|
|
41
|
+
<body>
|
|
42
|
+
<div id="app">
|
|
43
|
+
<div class="card header-content">
|
|
44
|
+
<div class="header">
|
|
45
|
+
<div class="dividerRight">
|
|
46
|
+
<img :src="'data:image/svg+xml;base64,${logoBase64 || ""}'" class="logo" alt="Bitpool" />
|
|
47
|
+
</div>
|
|
48
|
+
<div class="status">
|
|
49
|
+
<span @click="statusItemClicked" class="statusItem status-with-icon">
|
|
50
|
+
<span class="status-icon-wrapper" style="background-color: #10B981;">
|
|
51
|
+
<img src="data:image/svg+xml;base64,${iconBase64Map.ok}" class="status-icon ok-icon" alt="Points OK" />
|
|
52
|
+
</span>
|
|
53
|
+
<div class="status-text">
|
|
54
|
+
<span class="statBlockValue">
|
|
55
|
+
{{statCounts?.readCount}}
|
|
56
|
+
<span class="stat-percentage">{{statPercentages.readCount}}%</span>
|
|
57
|
+
</span>
|
|
58
|
+
<span class="statBlockKey">Points OK</span>
|
|
59
|
+
</div>
|
|
60
|
+
</span>
|
|
61
|
+
<span @click="statusItemClicked" class="statusItem status-with-icon">
|
|
62
|
+
<span class="status-icon-wrapper" style="background-color: #F1707B;">
|
|
63
|
+
<img src="data:image/svg+xml;base64,${iconBase64Map.error}" class="status-icon error-icon" alt="Points Error" />
|
|
64
|
+
</span>
|
|
65
|
+
<div class="status-text">
|
|
66
|
+
<span class="statBlockValue">
|
|
67
|
+
{{statCounts?.statBlock.error}}
|
|
68
|
+
<span class="stat-percentage">{{statPercentages.error}}%</span>
|
|
69
|
+
</span>
|
|
70
|
+
<span class="statBlockKey">Points Error</span>
|
|
71
|
+
</div>
|
|
72
|
+
</span>
|
|
73
|
+
<span @click="statusItemClicked" class="statusItem status-with-icon">
|
|
74
|
+
<span class="status-icon-wrapper" style="background-color: #133547;">
|
|
75
|
+
<img src="data:image/svg+xml;base64,${iconBase64Map.missing}" class="status-icon missing-icon" alt="Points Missing" />
|
|
76
|
+
</span>
|
|
77
|
+
<div class="status-text">
|
|
78
|
+
<span class="statBlockValue">
|
|
79
|
+
{{statCounts?.statBlock.missing}}
|
|
80
|
+
<span class="stat-percentage">{{statPercentages.missing}}%</span>
|
|
81
|
+
</span>
|
|
82
|
+
<span class="statBlockKey">Points Missing</span>
|
|
83
|
+
</div>
|
|
84
|
+
</span>
|
|
85
|
+
<span @click="statusItemClicked" class="statusItem status-with-icon">
|
|
86
|
+
<span class="status-icon-wrapper" style="background-color: #F59E0B;">
|
|
87
|
+
<img src="data:image/svg+xml;base64,${iconBase64Map.warning}" class="status-icon warning-icon" alt="Points Warning" />
|
|
88
|
+
</span>
|
|
89
|
+
<div class="status-text">
|
|
90
|
+
<span class="statBlockValue">
|
|
91
|
+
{{statCounts?.statBlock.warnings}}
|
|
92
|
+
<span class="stat-percentage">{{statPercentages.warnings}}%</span>
|
|
93
|
+
</span>
|
|
94
|
+
<span class="statBlockKey">Points Warnings</span>
|
|
95
|
+
</div>
|
|
96
|
+
</span>
|
|
97
|
+
<span @click="statusItemClicked" class="statusItem status-with-icon">
|
|
98
|
+
<span class="status-icon-wrapper" style="background-color: #00ADEF;">
|
|
99
|
+
<img src="data:image/svg+xml;base64,${iconBase64Map.unmapped}" class="status-icon unmapped-icon" alt="Points Unmapped" />
|
|
100
|
+
</span>
|
|
101
|
+
<div class="status-text">
|
|
102
|
+
<span class="statBlockValue">
|
|
103
|
+
{{statCounts?.statBlock.unmapped}}
|
|
104
|
+
<span class="stat-percentage">{{statPercentages.unmapped}}%</span>
|
|
105
|
+
</span>
|
|
106
|
+
<span class="statBlockKey">Points Unmapped</span>
|
|
107
|
+
</div>
|
|
108
|
+
</span>
|
|
109
|
+
<span @click="statusItemClicked" class="statusItem status-with-icon">
|
|
110
|
+
<span class="status-icon-wrapper" style="background-color: #0689BC;">
|
|
111
|
+
<img src="data:image/svg+xml;base64,${iconBase64Map.deviceIdChange}" class="status-icon device-id-change-icon" alt="Device ID Change" />
|
|
112
|
+
</span>
|
|
113
|
+
<div class="status-text">
|
|
114
|
+
<span class="statBlockValue">
|
|
115
|
+
{{statCounts?.statBlock.deviceIdChange}}
|
|
116
|
+
<span class="stat-percentage">{{statPercentages.deviceIdChange}}%</span>
|
|
117
|
+
</span>
|
|
118
|
+
<span class="statBlockKey">Changed Device ID's</span>
|
|
119
|
+
</div>
|
|
120
|
+
</span>
|
|
121
|
+
<span @click="statusItemClicked" class="statusItem status-with-icon">
|
|
122
|
+
<span class="status-icon-wrapper" style="background-color: #406C7D;">
|
|
123
|
+
<img src="data:image/svg+xml;base64,${iconBase64Map.deviceIdConflict || ''}" class="status-icon device-id-conflict-icon" alt="Device ID Conflict" />
|
|
124
|
+
</span>
|
|
125
|
+
<div class="status-text">
|
|
126
|
+
<span class="statBlockValue">
|
|
127
|
+
{{statCounts?.statBlock.deviceIdConflict}}
|
|
128
|
+
<span class="stat-percentage">{{statPercentages.deviceIdConflict}}%</span>
|
|
129
|
+
</span>
|
|
130
|
+
<span class="statBlockKey">Conflicting Device ID's</span>
|
|
131
|
+
</div>
|
|
132
|
+
</span>
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
<div class="content-wrapper">
|
|
137
|
+
<div class="card datatable-card">
|
|
138
|
+
<div class="center">
|
|
139
|
+
<p class="headertext">{{siteName}} - Point Status Display</p>
|
|
140
|
+
</div>
|
|
141
|
+
<p-datatable :value="tableData" paginator :rows="12" filterDisplay="menu" :filters="filters"
|
|
142
|
+
v-model:filters="filters" :sortMode="'multiple'" @filter="onFilter" scrollable scrollHeight="800px"
|
|
143
|
+
class="datatable fixed-height-table">
|
|
144
|
+
<template #header>
|
|
145
|
+
<div class="tableHeaderDiv">
|
|
146
|
+
<div style="margin-right: 5px">
|
|
147
|
+
<p-multiselect v-model="selectedColumns" :options="allColumns" optionLabel="header" dataKey="field"
|
|
148
|
+
placeholder="Select Columns" :maxSelectedLabels="3" class="columnSelector w-full md:w-20rem"
|
|
149
|
+
display="chip">
|
|
150
|
+
|
|
151
|
+
<template #option="slotProps">
|
|
152
|
+
<div style="display: flex; align-items: center;">
|
|
153
|
+
<div class="custom-checkbox">
|
|
154
|
+
<div :class="['custom-checkbox-box', {'selected': isSelected(slotProps.option)}]">
|
|
155
|
+
<i v-if="isSelected(slotProps.option)" class="pi pi-check custom-checkbox-icon"></i>
|
|
156
|
+
</div>
|
|
157
|
+
</div>
|
|
158
|
+
<span>{{ slotProps.option.header }}</span>
|
|
159
|
+
</div>
|
|
160
|
+
</template>
|
|
161
|
+
|
|
162
|
+
<template #value="slotProps">
|
|
163
|
+
<template v-if="slotProps.value && slotProps.value.length > 0">
|
|
164
|
+
<template v-if="slotProps.value.length <= 3">
|
|
165
|
+
<div class="p-multiselect-token" v-for="(item, index) in slotProps.value" :key="item.field">
|
|
166
|
+
<span class="p-multiselect-token-label">{{ item.header }}</span>
|
|
167
|
+
<span v-if="index < slotProps.value.length - 1">, </span>
|
|
168
|
+
</div>
|
|
169
|
+
</template>
|
|
170
|
+
<template v-else>
|
|
171
|
+
<div class="p-multiselect-token-label">{{ slotProps.value.length }} items selected</div>
|
|
172
|
+
</template>
|
|
173
|
+
</template>
|
|
174
|
+
<span v-else class="p-multiselect-placeholder">{{ 'Select Columns' }}</span>
|
|
175
|
+
</template>
|
|
176
|
+
</p-multiselect>
|
|
177
|
+
</div>
|
|
178
|
+
|
|
179
|
+
<div style="width: 100%">
|
|
180
|
+
<i class="pi pi-search searchBarContainer"></i>
|
|
181
|
+
<p-input-text class="searchBar" type="text" pInputText v-model="filters['global'].value"
|
|
182
|
+
placeholder="Search"></p-input-text>
|
|
183
|
+
</div>
|
|
184
|
+
</div>
|
|
185
|
+
|
|
186
|
+
</template>
|
|
187
|
+
<p-column v-for="col in visibleColumns" :key="col.field" :field="col.field" :header="col.header" sortable
|
|
188
|
+
filter></p-column>
|
|
189
|
+
<template #paginatorstart>
|
|
190
|
+
</template>
|
|
191
|
+
<template #paginatorend="slotProps">
|
|
192
|
+
<span class="">
|
|
193
|
+
<span class="statBlockValue">{{displayedRowsCount}} results</span>
|
|
194
|
+
</span>
|
|
195
|
+
</template>
|
|
196
|
+
</p-datatable>
|
|
197
|
+
</div>
|
|
198
|
+
</div>
|
|
199
|
+
</div>
|
|
200
|
+
|
|
201
|
+
<!-- Embedded scripts -->
|
|
202
|
+
<script>${embeddedDependencies.scripts.vue || ''}</script>
|
|
203
|
+
<script>${embeddedDependencies.scripts.primevue || ''}</script>
|
|
204
|
+
|
|
205
|
+
<script>
|
|
206
|
+
window.appData = ${JSON.stringify(appData)};
|
|
207
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
208
|
+
|
|
209
|
+
const { createApp } = Vue;
|
|
210
|
+
const app = createApp({
|
|
211
|
+
data() {
|
|
212
|
+
return {
|
|
213
|
+
tableData: window.appData.tableData || [],
|
|
214
|
+
filteredData: window.appData.tableData || [],
|
|
215
|
+
loading: false,
|
|
216
|
+
totalRecords: window.appData.tableData?.length || 0,
|
|
217
|
+
first: 0,
|
|
218
|
+
rows: 12,
|
|
219
|
+
lazyParams: {},
|
|
220
|
+
siteName: window.appData.siteName || "BACnet Inspector",
|
|
221
|
+
statCounts: window.appData.statCounts || {},
|
|
222
|
+
displayedRowsCount: window.appData.tableData?.length || 0,
|
|
223
|
+
filters: {
|
|
224
|
+
global: { value: null, matchMode: "contains" },
|
|
225
|
+
deviceID: { value: null, matchMode: "contains" },
|
|
226
|
+
objectType: { value: null, matchMode: "contains" },
|
|
227
|
+
objectInstance: { value: null, matchMode: "contains" },
|
|
228
|
+
presentValue: { value: null, matchMode: "contains" },
|
|
229
|
+
dataModelStatus: { value: null, matchMode: "contains" },
|
|
230
|
+
pointName: { value: null, matchMode: "contains" },
|
|
231
|
+
discoveredBACnetPointName: { value: null, matchMode: "contains" },
|
|
232
|
+
displayName: { value: null, matchMode: "contains" },
|
|
233
|
+
deviceName: { value: null, matchMode: "contains" },
|
|
234
|
+
ipAddress: { value: null, matchMode: "contains" },
|
|
235
|
+
area: { value: null, matchMode: "contains" },
|
|
236
|
+
key: { value: null, matchMode: "contains" },
|
|
237
|
+
topic: { value: null, matchMode: "contains" },
|
|
238
|
+
lastSeen: { value: null, matchMode: "contains" },
|
|
239
|
+
error: { value: null, matchMode: "contains" },
|
|
240
|
+
},
|
|
241
|
+
allColumns: [
|
|
242
|
+
{ field: "deviceID", header: "Device ID" },
|
|
243
|
+
{ field: "objectType", header: "Object Type" },
|
|
244
|
+
{ field: "objectInstance", header: "Object Instance" },
|
|
245
|
+
{ field: "presentValue", header: "Present Value" },
|
|
246
|
+
{ field: "dataModelStatus", header: "Data Model Status" },
|
|
247
|
+
{ field: "pointName", header: "Mapped Point Name" },
|
|
248
|
+
{ field: "discoveredBACnetPointName", header: "Discovered Point Name" },
|
|
249
|
+
{ field: "displayName", header: "Display Name" },
|
|
250
|
+
{ field: "deviceName", header: "Device Name" },
|
|
251
|
+
{ field: "ipAddress", header: "IP Address" },
|
|
252
|
+
{ field: "area", header: "Area" },
|
|
253
|
+
{ field: "key", header: "Key" },
|
|
254
|
+
{ field: "topic", header: "Topic" },
|
|
255
|
+
{ field: "lastSeen", header: "Last Seen" },
|
|
256
|
+
{ field: "error", header: "Error" },
|
|
257
|
+
],
|
|
258
|
+
selectedColumns: [],
|
|
259
|
+
statPercentages: {
|
|
260
|
+
readCount: 0,
|
|
261
|
+
error: 0,
|
|
262
|
+
missing: 0,
|
|
263
|
+
warnings: 0,
|
|
264
|
+
deviceIdChange: 0,
|
|
265
|
+
deviceIdConflict: 0,
|
|
266
|
+
unmapped: 0
|
|
267
|
+
},
|
|
268
|
+
activeFilter: null,
|
|
269
|
+
};
|
|
270
|
+
},
|
|
271
|
+
mounted() {
|
|
272
|
+
this.selectedColumns = JSON.parse(JSON.stringify(this.allColumns));
|
|
273
|
+
if(window.appData.tableData.length > 0) {
|
|
274
|
+
this.tableData = window.appData.tableData;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Calculate percentages
|
|
278
|
+
const total = this.tableData.length || 1; // Avoid division by zero
|
|
279
|
+
this.statPercentages = {
|
|
280
|
+
readCount: Math.round((this.statCounts.readCount / total) * 100) || 0,
|
|
281
|
+
error: Math.round((this.statCounts.statBlock?.error / total) * 100) || 0,
|
|
282
|
+
missing: Math.round((this.statCounts.statBlock?.missing / total) * 100) || 0,
|
|
283
|
+
warnings: Math.round((this.statCounts.statBlock?.warnings / total) * 100) || 0,
|
|
284
|
+
deviceIdChange: Math.round((this.statCounts.statBlock?.deviceIdChange / total) * 100) || 0,
|
|
285
|
+
deviceIdConflict: Math.round((this.statCounts.statBlock?.deviceIdConflict / total) * 100) || 0,
|
|
286
|
+
unmapped: Math.round((this.statCounts.statBlock?.unmapped / total) * 100) || 0
|
|
287
|
+
};
|
|
288
|
+
},
|
|
289
|
+
computed: {
|
|
290
|
+
visibleColumns() {
|
|
291
|
+
return this.selectedColumns || this.allColumns;
|
|
292
|
+
},
|
|
293
|
+
displayData() {
|
|
294
|
+
if (!this.tableData || this.tableData.length === 0) {
|
|
295
|
+
// Create empty rows to maintain height
|
|
296
|
+
return Array(20).fill({}).map(() => ({}));
|
|
297
|
+
}
|
|
298
|
+
return this.tableData;
|
|
299
|
+
}
|
|
300
|
+
},
|
|
301
|
+
methods: {
|
|
302
|
+
statusItemClicked(e) {
|
|
303
|
+
// Get the filter category from the clicked item's text content
|
|
304
|
+
const clickedItem = e.currentTarget;
|
|
305
|
+
const statusType = clickedItem.querySelector('.statBlockKey').textContent.trim();
|
|
306
|
+
|
|
307
|
+
// Clear previous active status styling
|
|
308
|
+
document.querySelectorAll('.statusItem').forEach(item => {
|
|
309
|
+
item.classList.remove('active-filter');
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
// If clicking the already active filter, clear it
|
|
313
|
+
if (this.activeFilter === statusType) {
|
|
314
|
+
this.activeFilter = null;
|
|
315
|
+
this.filters['dataModelStatus'].value = null;
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Set this item as active
|
|
320
|
+
clickedItem.classList.add('active-filter');
|
|
321
|
+
this.activeFilter = statusType;
|
|
322
|
+
|
|
323
|
+
// Apply the appropriate filter based on which status item was clicked
|
|
324
|
+
let filterValue = '';
|
|
325
|
+
switch (statusType) {
|
|
326
|
+
case 'Points OK':
|
|
327
|
+
filterValue = 'Point Ok';
|
|
328
|
+
break;
|
|
329
|
+
case 'Points Error':
|
|
330
|
+
filterValue = 'Point Error';
|
|
331
|
+
break;
|
|
332
|
+
case 'Points Missing':
|
|
333
|
+
filterValue = 'Point Missing';
|
|
334
|
+
break;
|
|
335
|
+
case 'Points Warnings':
|
|
336
|
+
filterValue = 'Point Warning';
|
|
337
|
+
break;
|
|
338
|
+
case 'Points Unmapped':
|
|
339
|
+
filterValue = 'Point Unmapped';
|
|
340
|
+
break;
|
|
341
|
+
case "Changed Device ID's":
|
|
342
|
+
filterValue = 'Device ID Changed';
|
|
343
|
+
break;
|
|
344
|
+
case "Conflicting Device ID's":
|
|
345
|
+
filterValue = 'Device ID Conflict';
|
|
346
|
+
break;
|
|
347
|
+
default:
|
|
348
|
+
filterValue = '';
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Apply the filter
|
|
352
|
+
this.filters['dataModelStatus'].value = filterValue;
|
|
353
|
+
},
|
|
354
|
+
isSelected(option) {
|
|
355
|
+
if (!this.selectedColumns) return false;
|
|
356
|
+
return this.selectedColumns.some((item) => item.field === option.field);
|
|
357
|
+
},
|
|
358
|
+
getRowClass(rowData) {
|
|
359
|
+
if (!rowData || !rowData.dataModelStatus) return "";
|
|
360
|
+
if (rowData.dataModelStatus.includes("Point OK")) return "row-ok";
|
|
361
|
+
if (rowData.dataModelStatus.includes("Point Error")) return "row-error";
|
|
362
|
+
if (rowData.dataModelStatus.includes("Point Warning")) return "row-warning";
|
|
363
|
+
if (rowData.dataModelStatus.includes("Point Missing")) return "row-missing";
|
|
364
|
+
return "";
|
|
365
|
+
},
|
|
366
|
+
onGlobalFilterChange(event) {
|
|
367
|
+
const value = event.target.value;
|
|
368
|
+
this.filters.global.value = value;
|
|
369
|
+
if (this.$refs.dt) {
|
|
370
|
+
this.$refs.dt.filter(value, "global", "contains");
|
|
371
|
+
}
|
|
372
|
+
},
|
|
373
|
+
onFilter(e) {
|
|
374
|
+
this.displayedRowsCount = e.filteredValue ? e.filteredValue.length : 0;
|
|
375
|
+
},
|
|
376
|
+
onPage(event) {
|
|
377
|
+
this.first = event.first;
|
|
378
|
+
this.rows = event.rows;
|
|
379
|
+
},
|
|
380
|
+
},
|
|
381
|
+
components: {
|
|
382
|
+
"p-button": PrimeVue.Button,
|
|
383
|
+
"p-card": PrimeVue.Card,
|
|
384
|
+
"p-input-text": PrimeVue.InputText,
|
|
385
|
+
"p-datatable": PrimeVue.DataTable,
|
|
386
|
+
"p-column": PrimeVue.Column,
|
|
387
|
+
"p-icon-field": PrimeVue.IconField,
|
|
388
|
+
"p-input-icon": PrimeVue.InputIcon,
|
|
389
|
+
"p-multiselect": PrimeVue.MultiSelect
|
|
390
|
+
},
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
app.use(PrimeVue.Config);
|
|
394
|
+
app.mount('#app');
|
|
395
|
+
});
|
|
396
|
+
</script>
|
|
397
|
+
</body>
|
|
398
|
+
</html>`;
|
|
399
|
+
|
|
400
|
+
return fullHtml;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* In a Node.js environment, fetch the logo and convert to base64
|
|
405
|
+
* @param {string} logoPath - Path to the logo file
|
|
406
|
+
* @returns {Promise<string>} - Base64 encoded logo
|
|
407
|
+
*/
|
|
408
|
+
async function getLogoAsBase64(logoPath) {
|
|
409
|
+
// In Node.js environment
|
|
410
|
+
if (typeof window === "undefined" && logoPath) {
|
|
411
|
+
try {
|
|
412
|
+
const fs = require("fs");
|
|
413
|
+
const logoData = fs.readFileSync(logoPath);
|
|
414
|
+
return Buffer.from(logoData).toString("base64");
|
|
415
|
+
} catch (error) {
|
|
416
|
+
console.error("Error reading logo file:", error);
|
|
417
|
+
return "";
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// In browser environment (if somehow used there)
|
|
422
|
+
if (typeof window !== "undefined" && logoPath) {
|
|
423
|
+
try {
|
|
424
|
+
const response = await fetch(logoPath);
|
|
425
|
+
const blob = await response.blob();
|
|
426
|
+
return new Promise((resolve) => {
|
|
427
|
+
const reader = new FileReader();
|
|
428
|
+
reader.onloadend = () => {
|
|
429
|
+
const base64 = reader.result.split(",")[1];
|
|
430
|
+
resolve(base64);
|
|
431
|
+
};
|
|
432
|
+
reader.readAsDataURL(blob);
|
|
433
|
+
});
|
|
434
|
+
} catch (error) {
|
|
435
|
+
console.error("Error fetching logo:", error);
|
|
436
|
+
return "";
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
return "";
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
async function getIconsAsBase64() {
|
|
444
|
+
const fs = require('fs');
|
|
445
|
+
const path = require('path');
|
|
446
|
+
|
|
447
|
+
const icons = {
|
|
448
|
+
ok: 'points-ok-icon.svg',
|
|
449
|
+
error: 'points-error-icon.svg',
|
|
450
|
+
missing: 'points-missing-icon.svg',
|
|
451
|
+
warning: 'points-warning-icon.svg',
|
|
452
|
+
deviceIdChange: 'device-id-change-icon.svg',
|
|
453
|
+
unmapped: 'points-unmapped-icon.svg',
|
|
454
|
+
deviceIdConflict: 'device-id-conflict-icon.svg'
|
|
455
|
+
};
|
|
456
|
+
|
|
457
|
+
const iconBase64Map = {};
|
|
458
|
+
|
|
459
|
+
for (const [key, filename] of Object.entries(icons)) {
|
|
460
|
+
try {
|
|
461
|
+
// Construct the full path to the icon file
|
|
462
|
+
const iconPath = path.join(__dirname, 'resources/icons', filename);
|
|
463
|
+
|
|
464
|
+
// Read the file synchronously
|
|
465
|
+
const fileContent = fs.readFileSync(iconPath, 'utf8');
|
|
466
|
+
|
|
467
|
+
// Convert SVG content to base64
|
|
468
|
+
const base64 = Buffer.from(fileContent).toString('base64');
|
|
469
|
+
|
|
470
|
+
iconBase64Map[key] = base64;
|
|
471
|
+
} catch (error) {
|
|
472
|
+
console.error(`Error reading ${filename}:`, error);
|
|
473
|
+
iconBase64Map[key] = '';
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
return iconBase64Map;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Get embedded dependencies content for the PrimeVue app
|
|
482
|
+
* @returns {Object} - Object with embedded scripts and styles
|
|
483
|
+
*/
|
|
484
|
+
async function getEmbeddedDependencies() {
|
|
485
|
+
const fs = require('fs');
|
|
486
|
+
const path = require('path');
|
|
487
|
+
|
|
488
|
+
// Define files to embed
|
|
489
|
+
const dependencyFiles = {
|
|
490
|
+
scripts: [
|
|
491
|
+
{ name: 'vue', path: path.join(__dirname, "resources", "vue3513.global.prod.js") },
|
|
492
|
+
{ name: 'primevue', path: path.join(__dirname, 'resources', 'primevue.min.js') }
|
|
493
|
+
],
|
|
494
|
+
styles: [
|
|
495
|
+
{ name: 'theme', path: path.join(__dirname, 'resources', 'primevue-saga-blue-theme.css') },
|
|
496
|
+
{ name: 'primevue', path: path.join(__dirname, 'resources', 'primevue.min.css') },
|
|
497
|
+
{ name: 'primeflex', path: path.join(__dirname, 'resources', 'primeflex.min.css') },
|
|
498
|
+
{ name: 'primeicons', path: path.join(__dirname, 'resources', 'primeicons.css') },
|
|
499
|
+
{ name: 'inspector', path: path.join(__dirname, 'resources', 'inspectorStyles.css') }
|
|
500
|
+
]
|
|
501
|
+
};
|
|
502
|
+
|
|
503
|
+
// Read and collect dependencies
|
|
504
|
+
const embeddedContent = {
|
|
505
|
+
scripts: {},
|
|
506
|
+
styles: {}
|
|
507
|
+
};
|
|
508
|
+
|
|
509
|
+
// Read script files
|
|
510
|
+
for (const script of dependencyFiles.scripts) {
|
|
511
|
+
try {
|
|
512
|
+
embeddedContent.scripts[script.name] = fs.readFileSync(script.path, 'utf8');
|
|
513
|
+
} catch (error) {
|
|
514
|
+
console.error(`Error reading script file ${script.name}:`, error);
|
|
515
|
+
embeddedContent.scripts[script.name] = `console.error("Failed to load ${script.name}");`;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// Read style files
|
|
520
|
+
for (const style of dependencyFiles.styles) {
|
|
521
|
+
try {
|
|
522
|
+
embeddedContent.styles[style.name] = fs.readFileSync(style.path, 'utf8');
|
|
523
|
+
} catch (error) {
|
|
524
|
+
console.error(`Error reading style file ${style.name}:`, error);
|
|
525
|
+
embeddedContent.styles[style.name] = `/* Failed to load ${style.name} */`;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
return embeddedContent;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
module.exports = {
|
|
533
|
+
generatePrimeVueAppHtmlStatic,
|
|
534
|
+
getLogoAsBase64,
|
|
535
|
+
};
|
package/treeBuilder.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const {
|
|
1
|
+
const { queueConfigStore } = require("./common");
|
|
2
2
|
const { BacnetDevice } = require("./bacnet_device");
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -33,12 +33,12 @@ class treeBuilder {
|
|
|
33
33
|
*/
|
|
34
34
|
cacheData() {
|
|
35
35
|
// Cache the current state of the network tree and other data
|
|
36
|
-
|
|
36
|
+
queueConfigStore({
|
|
37
37
|
renderList: this.renderList,
|
|
38
38
|
deviceList: this.deviceList,
|
|
39
39
|
pointList: this.networkTree,
|
|
40
40
|
renderListCount: this.renderListCount,
|
|
41
|
-
})
|
|
41
|
+
});
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
/**
|