@signalk/charts-plugin 3.2.0 → 3.5.0-beta
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/.mocharc.cjs +5 -0
- package/.prettierignore +7 -0
- package/README.md +45 -16
- package/package.json +27 -24
- package/plugin/chartDownloader.d.ts +4 -7
- package/plugin/chartDownloader.d.ts.map +1 -0
- package/plugin/chartDownloader.js +88 -51
- package/plugin/chartDownloader.js.map +1 -0
- package/plugin/charts.d.ts +5 -1
- package/plugin/charts.d.ts.map +1 -0
- package/plugin/charts.js +152 -67
- package/plugin/charts.js.map +1 -0
- package/plugin/constants.d.ts +1 -0
- package/plugin/constants.d.ts.map +1 -0
- package/plugin/constants.js +1 -0
- package/plugin/constants.js.map +1 -0
- package/plugin/index.d.ts +13 -1
- package/plugin/index.d.ts.map +1 -0
- package/plugin/index.js +384 -102
- package/plugin/index.js.map +1 -0
- package/plugin/projection.d.ts +7 -0
- package/plugin/projection.d.ts.map +1 -0
- package/plugin/projection.js +42 -0
- package/plugin/projection.js.map +1 -0
- package/plugin/types.d.ts +1 -0
- package/plugin/types.d.ts.map +1 -0
- package/plugin/types.js +1 -0
- package/plugin/types.js.map +1 -0
- package/public/index.html +106 -83
- package/public/index.js +274 -267
- package/public/style.css +65 -63
- package/stryker.conf.json +26 -0
- package/tsconfig.build.json +17 -0
package/public/index.js
CHANGED
|
@@ -1,195 +1,199 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
(function () {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
})();
|
|
1
|
+
;(function () {})()
|
|
8
2
|
|
|
9
3
|
document.addEventListener('DOMContentLoaded', async () => {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
4
|
+
const regionSelect = document.getElementById('region')
|
|
5
|
+
const bboxInputs = document.querySelectorAll(
|
|
6
|
+
'#bboxMinLon, #bboxMinLat, #bboxMaxLon, #bboxMaxLat'
|
|
7
|
+
)
|
|
8
|
+
const tileInputs = document.querySelectorAll('#tileX, #tileY, #tileZ')
|
|
9
|
+
|
|
10
|
+
function updateInputs() {
|
|
11
|
+
const type = document.querySelector('input[name="inputType"]:checked').value
|
|
12
|
+
|
|
13
|
+
regionSelect.disabled = type !== 'region'
|
|
14
|
+
bboxInputs.forEach((i) => (i.disabled = type !== 'bbox'))
|
|
15
|
+
tileInputs.forEach((i) => (i.disabled = type !== 'tile'))
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Attach listeners
|
|
19
|
+
document.querySelectorAll('input[name="inputType"]').forEach((r) => {
|
|
20
|
+
r.addEventListener('change', updateInputs)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
updateInputs() // initial load
|
|
24
|
+
|
|
25
|
+
if (!regionSelect) return
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
const resp = await fetch('/signalk/v2/api/resources/regions')
|
|
29
|
+
if (!resp.ok) throw new Error(`Failed to fetch regions: ${resp.status}`)
|
|
30
|
+
const data = await resp.json()
|
|
31
|
+
// clear existing options
|
|
32
|
+
regionSelect.innerHTML = ''
|
|
33
|
+
|
|
34
|
+
const entries = Object.entries(data || {})
|
|
35
|
+
entries
|
|
36
|
+
.sort(([, a], [, b]) => {
|
|
37
|
+
const na = a && a.name ? String(a.name) : ''
|
|
38
|
+
const nb = b && b.name ? String(b.name) : ''
|
|
39
|
+
return na.localeCompare(nb)
|
|
40
|
+
})
|
|
41
|
+
.forEach(([id, info]) => {
|
|
42
|
+
const opt = document.createElement('option')
|
|
43
|
+
opt.value = id
|
|
44
|
+
opt.textContent = info && info.name ? info.name : id
|
|
45
|
+
regionSelect.appendChild(opt)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
// if no regions returned, add a fallback option
|
|
49
|
+
if (!regionSelect.options.length) {
|
|
50
|
+
const opt = document.createElement('option')
|
|
51
|
+
opt.value = ''
|
|
52
|
+
opt.textContent = '-- No regions available --'
|
|
53
|
+
regionSelect.appendChild(opt)
|
|
20
54
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
regionSelect.appendChild(opt);
|
|
50
|
-
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
// if no regions returned, add a fallback option
|
|
54
|
-
if (!regionSelect.options.length) {
|
|
55
|
-
const opt = document.createElement('option');
|
|
56
|
-
opt.value = '';
|
|
57
|
-
opt.textContent = '-- No regions available --';
|
|
58
|
-
regionSelect.appendChild(opt);
|
|
55
|
+
} catch (err) {
|
|
56
|
+
console.error('Error loading regions:', err)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
//Fetch available maps and populate the chart select box
|
|
60
|
+
const chartSelect = document.getElementById('chart')
|
|
61
|
+
if (!chartSelect) return
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
const resp = await fetch('/signalk/v2/api/resources/charts')
|
|
65
|
+
if (!resp.ok) throw new Error(`Failed to fetch charts: ${resp.status}`)
|
|
66
|
+
const data = await resp.json()
|
|
67
|
+
// clear existing options
|
|
68
|
+
chartSelect.innerHTML = ''
|
|
69
|
+
|
|
70
|
+
const entries = Object.entries(data || {})
|
|
71
|
+
entries
|
|
72
|
+
.sort(([, a], [, b]) => {
|
|
73
|
+
const na = a && a.name ? String(a.name) : ''
|
|
74
|
+
const nb = b && b.name ? String(b.name) : ''
|
|
75
|
+
return na.localeCompare(nb)
|
|
76
|
+
})
|
|
77
|
+
.forEach(([id, info]) => {
|
|
78
|
+
if (info.proxy === true) {
|
|
79
|
+
const opt = document.createElement('option')
|
|
80
|
+
opt.value = id
|
|
81
|
+
opt.textContent = info && info.name ? info.name : id
|
|
82
|
+
chartSelect.appendChild(opt)
|
|
59
83
|
}
|
|
60
|
-
|
|
61
|
-
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
// if no maps returned, add a fallback option
|
|
87
|
+
if (!chartSelect.options.length) {
|
|
88
|
+
const opt = document.createElement('option')
|
|
89
|
+
opt.value = ''
|
|
90
|
+
opt.textContent = '-- No maps available --'
|
|
91
|
+
chartSelect.appendChild(opt)
|
|
62
92
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
const nb = (b && b.name) ? String(b.name) : '';
|
|
80
|
-
return na.localeCompare(nb);
|
|
81
|
-
})
|
|
82
|
-
.forEach(([id, info]) => {
|
|
83
|
-
|
|
84
|
-
if (info.proxy === true) {
|
|
85
|
-
const opt = document.createElement('option');
|
|
86
|
-
opt.value = id;
|
|
87
|
-
opt.textContent = (info && info.name) ? info.name : id;
|
|
88
|
-
chartSelect.appendChild(opt);
|
|
89
|
-
}
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
// if no maps returned, add a fallback option
|
|
93
|
-
if (!chartSelect.options.length) {
|
|
94
|
-
const opt = document.createElement('option');
|
|
95
|
-
opt.value = '';
|
|
96
|
-
opt.textContent = '-- No maps available --';
|
|
97
|
-
chartSelect.appendChild(opt);
|
|
93
|
+
} catch (err) {
|
|
94
|
+
console.error('Error loading maps:', err)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
document
|
|
98
|
+
.getElementById('createJobButton')
|
|
99
|
+
.addEventListener('click', async () => {
|
|
100
|
+
try {
|
|
101
|
+
const chart = document.getElementById('chart').value
|
|
102
|
+
const regionGUID = document.getElementById('region').value
|
|
103
|
+
const maxZoom = document.getElementById('maxZoom').value
|
|
104
|
+
const bbox = {
|
|
105
|
+
minLon: parseFloat(document.getElementById('bboxMinLon').value),
|
|
106
|
+
minLat: parseFloat(document.getElementById('bboxMinLat').value),
|
|
107
|
+
maxLon: parseFloat(document.getElementById('bboxMaxLon').value),
|
|
108
|
+
maxLat: parseFloat(document.getElementById('bboxMaxLat').value)
|
|
98
109
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
// const tile = {
|
|
115
|
-
// z: parseInt(document.getElementById('tileZ').value),
|
|
116
|
-
// x: parseInt(document.getElementById('tileX').value),
|
|
117
|
-
// y: parseInt(document.getElementById('tileY').value),
|
|
118
|
-
|
|
119
|
-
// };
|
|
120
|
-
// console.log(tile);
|
|
121
|
-
const type = document.querySelector('input[name="inputType"]:checked').value;
|
|
122
|
-
const body = {};
|
|
123
|
-
if (type === 'region') {
|
|
124
|
-
body.regionGUID = regionGUID;
|
|
125
|
-
} else if (type === 'bbox') {
|
|
126
|
-
body.bbox = bbox;
|
|
127
|
-
}
|
|
128
|
-
// else if (type === 'tile') {
|
|
129
|
-
// body.tile = tile;
|
|
130
|
-
// }
|
|
131
|
-
body.maxZoom = maxZoom;
|
|
132
|
-
const resp = await fetch(`/signalk/chart-tiles/cache/${chart}`, {
|
|
133
|
-
method: 'POST',
|
|
134
|
-
headers: {
|
|
135
|
-
'Content-Type': 'application/json'
|
|
136
|
-
},
|
|
137
|
-
body: JSON.stringify(body)
|
|
138
|
-
});
|
|
139
|
-
if (!resp.ok) throw new Error(`Failed to seed charts: ${resp.status}`);
|
|
140
|
-
const data = await resp.json();
|
|
141
|
-
} catch (err) {
|
|
142
|
-
console.error('Error seeding charts:', err);
|
|
110
|
+
// const tile = {
|
|
111
|
+
// z: parseInt(document.getElementById('tileZ').value),
|
|
112
|
+
// x: parseInt(document.getElementById('tileX').value),
|
|
113
|
+
// y: parseInt(document.getElementById('tileY').value),
|
|
114
|
+
|
|
115
|
+
// };
|
|
116
|
+
// console.log(tile);
|
|
117
|
+
const type = document.querySelector(
|
|
118
|
+
'input[name="inputType"]:checked'
|
|
119
|
+
).value
|
|
120
|
+
const body = {}
|
|
121
|
+
if (type === 'region') {
|
|
122
|
+
body.regionGUID = regionGUID
|
|
123
|
+
} else if (type === 'bbox') {
|
|
124
|
+
body.bbox = bbox
|
|
143
125
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
126
|
+
// else if (type === 'tile') {
|
|
127
|
+
// body.tile = tile;
|
|
128
|
+
// }
|
|
129
|
+
body.maxZoom = maxZoom
|
|
130
|
+
const resp = await fetch(`/signalk/chart-tiles/cache/${chart}`, {
|
|
131
|
+
method: 'POST',
|
|
132
|
+
headers: {
|
|
133
|
+
'Content-Type': 'application/json'
|
|
134
|
+
},
|
|
135
|
+
body: JSON.stringify(body)
|
|
136
|
+
})
|
|
137
|
+
if (!resp.ok) throw new Error(`Failed to seed charts: ${resp.status}`)
|
|
138
|
+
const data = await resp.json()
|
|
139
|
+
} catch (err) {
|
|
140
|
+
console.error('Error seeding charts:', err)
|
|
141
|
+
}
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
const fmt = (n) => (typeof n === 'number' ? n.toLocaleString() : '-')
|
|
145
|
+
const statusText = (s) => {
|
|
146
|
+
switch (s) {
|
|
147
|
+
case 0:
|
|
148
|
+
return 'Stopped'
|
|
149
|
+
case 1:
|
|
150
|
+
return 'Running'
|
|
151
|
+
default:
|
|
152
|
+
return String(s)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function renderJobs(items) {
|
|
157
|
+
const tbody = document.querySelector('#activeJobsTable tbody')
|
|
158
|
+
if (!tbody) return
|
|
159
|
+
tbody.innerHTML = ''
|
|
160
|
+
if (!Array.isArray(items) || items.length === 0) {
|
|
161
|
+
const tr = document.createElement('tr')
|
|
162
|
+
tr.innerHTML = '<td colspan="10" class="muted">No active jobs</td>'
|
|
163
|
+
tbody.appendChild(tr)
|
|
164
|
+
return
|
|
165
|
+
}
|
|
168
166
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
167
|
+
items.forEach((it, idx) => {
|
|
168
|
+
const tr = document.createElement('tr')
|
|
169
|
+
|
|
170
|
+
const pct =
|
|
171
|
+
typeof it.progress === 'number' && isFinite(it.progress)
|
|
172
|
+
? Math.max(0, Math.min(100, it.progress * 100))
|
|
173
|
+
: 0
|
|
174
|
+
const pctText = `${pct.toFixed(1)}%`
|
|
175
|
+
tr.innerHTML = [
|
|
176
|
+
`<td>${idx + 1}</td>`,
|
|
177
|
+
`<td>${it.chartName || '-'}</td>`,
|
|
178
|
+
`<td>${it.regionName || '-'}</td>`,
|
|
179
|
+
`<td>${fmt(it.totalTiles)}</td>`,
|
|
180
|
+
`<td>${fmt(it.downloadedTiles)}</td>`,
|
|
181
|
+
`<td>${fmt(it.cachedTiles)}</td>`,
|
|
182
|
+
`<td>${fmt(it.failedTiles)}</td>`,
|
|
183
|
+
`<td>
|
|
180
184
|
<span class="progress-bar" aria-hidden="true">
|
|
181
185
|
<span class="progress-fill" style="width:${pct}%"></span>
|
|
182
186
|
</span>
|
|
183
187
|
<span class="percent">${pctText}</span>
|
|
184
188
|
</td>`,
|
|
185
|
-
|
|
186
|
-
|
|
189
|
+
`<td>${statusText(it.status)}</td>`,
|
|
190
|
+
`
|
|
187
191
|
<td class="action-buttons">
|
|
188
192
|
<button class="btn startstop-btn" data-id="${it.id}" data-totalTiles="${it.totalTiles}" title="Start">
|
|
189
|
-
<svg class="icon-play ${it.status === 1 ?
|
|
193
|
+
<svg class="icon-play ${it.status === 1 ? 'hidden' : ''}" viewBox="0 0 24 24" width="16" height="16" fill="white">
|
|
190
194
|
<path d="M8 5v14l11-7z"/>
|
|
191
195
|
</svg>
|
|
192
|
-
<svg class="icon-pause ${it.status === 1 ?
|
|
196
|
+
<svg class="icon-pause ${it.status === 1 ? '' : 'hidden'}" viewBox="0 0 24 24" width="16" height="16" fill="white">
|
|
193
197
|
<path d="M6 19h4V5H6zm8-14v14h4V5z"/>
|
|
194
198
|
</svg>
|
|
195
199
|
</button>
|
|
@@ -205,99 +209,102 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|
|
205
209
|
</button>
|
|
206
210
|
</td>
|
|
207
211
|
`
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
212
|
+
].join('')
|
|
213
|
+
|
|
214
|
+
tr.querySelectorAll('.startstop-btn').forEach((button) => {
|
|
215
|
+
button.addEventListener('click', () => {
|
|
216
|
+
const playIcon = button.querySelector('.icon-play')
|
|
217
|
+
const pauseIcon = button.querySelector('.icon-pause')
|
|
218
|
+
const jobId = button.getAttribute('data-id')
|
|
219
|
+
const totalTiles = button.getAttribute('data-totalTiles')
|
|
220
|
+
const isRunning = pauseIcon.classList.contains('hidden')
|
|
221
|
+
console.log(isRunning)
|
|
222
|
+
if (isRunning) {
|
|
223
|
+
// Switch to stop
|
|
224
|
+
if (
|
|
225
|
+
totalTiles > 100000 &&
|
|
226
|
+
!confirm(
|
|
227
|
+
`This job has more than ${totalTiles} tiles to download. Starting it may take a long time and put high load on the server. Are you sure you want to start it?`
|
|
228
|
+
)
|
|
229
|
+
) {
|
|
230
|
+
return
|
|
231
|
+
}
|
|
232
|
+
takeAction(jobId, 'start')
|
|
233
|
+
button.title = 'Stop'
|
|
234
|
+
} else {
|
|
235
|
+
// Switch to start
|
|
236
|
+
takeAction(jobId, 'stop')
|
|
237
|
+
button.title = 'Start'
|
|
238
|
+
}
|
|
239
|
+
})
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
tr.querySelectorAll('.delete-btn').forEach((button) => {
|
|
243
|
+
button.addEventListener('click', () => {
|
|
244
|
+
if (
|
|
245
|
+
confirm(
|
|
246
|
+
'This will delete all cached tiles for this job. Are you sure?'
|
|
247
|
+
)
|
|
248
|
+
) {
|
|
249
|
+
const jobId = button.getAttribute('data-id')
|
|
250
|
+
if (jobId) {
|
|
251
|
+
takeAction(jobId, 'delete')
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
})
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
tr.querySelectorAll('.remove-btn').forEach((button) => {
|
|
258
|
+
button.addEventListener('click', () => {
|
|
259
|
+
const jobId = button.getAttribute('data-id')
|
|
260
|
+
if (jobId) {
|
|
261
|
+
takeAction(jobId, 'remove')
|
|
262
|
+
}
|
|
263
|
+
})
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
tbody.appendChild(tr)
|
|
267
|
+
})
|
|
268
|
+
|
|
269
|
+
function takeAction(jobId, action) {
|
|
270
|
+
fetch(`/signalk/chart-tiles/cache/jobs/${jobId}`, {
|
|
271
|
+
method: 'POST',
|
|
272
|
+
headers: {
|
|
273
|
+
'Content-Type': 'application/json'
|
|
274
|
+
},
|
|
275
|
+
body: JSON.stringify({
|
|
276
|
+
action: action
|
|
277
|
+
})
|
|
278
|
+
})
|
|
279
|
+
.then((response) => {
|
|
280
|
+
if (!response.ok) {
|
|
281
|
+
throw new Error(
|
|
282
|
+
`Failed to ${action} job ${jobId}: ${response.status}`
|
|
283
|
+
)
|
|
284
|
+
}
|
|
285
|
+
fetchActiveJobs()
|
|
286
|
+
})
|
|
287
|
+
.catch((error) => {
|
|
288
|
+
console.error(`Error ${action} job:`, error)
|
|
289
|
+
})
|
|
278
290
|
}
|
|
291
|
+
}
|
|
279
292
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
tbody.innerHTML = '<tr><td colspan="10" class="muted">Error loading active jobs</td></tr>';
|
|
292
|
-
}
|
|
293
|
+
async function fetchActiveJobs() {
|
|
294
|
+
const tbody = document.querySelector('#activeJobsTable tbody')
|
|
295
|
+
try {
|
|
296
|
+
const resp = await fetch('/signalk/chart-tiles/cache/jobs')
|
|
297
|
+
if (!resp.ok) throw new Error('HTTP ' + resp.status)
|
|
298
|
+
const data = await resp.json()
|
|
299
|
+
renderJobs(data)
|
|
300
|
+
} catch (err) {
|
|
301
|
+
console.error('Error fetching active jobs:', err)
|
|
302
|
+
tbody.innerHTML =
|
|
303
|
+
'<tr><td colspan="10" class="muted">Error loading active jobs</td></tr>'
|
|
293
304
|
}
|
|
305
|
+
}
|
|
294
306
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
setInterval(fetchActiveJobs, 2000);
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
});
|
|
303
|
-
|
|
307
|
+
fetchActiveJobs()
|
|
308
|
+
// refresh every 2 seconds
|
|
309
|
+
setInterval(fetchActiveJobs, 2000)
|
|
310
|
+
})
|