pmtiles-swarm 0.27.0 โ 0.28.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 +21 -0
- package/package.json +1 -1
- package/src/web/index.html +103 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,27 @@
|
|
|
7
7
|
### ๐ Bug fixes
|
|
8
8
|
- _...Add new stuff here..._
|
|
9
9
|
|
|
10
|
+
## 0.28.0
|
|
11
|
+
### โจ Features and improvements
|
|
12
|
+
- **The archive list can be searched and sorted, and says when each archive was added.** `Added` is
|
|
13
|
+
a column now โ `createdAt` has always been on every entry and returned by `/api/catalog`, it was
|
|
14
|
+
simply never shown โ with a date for anything older than today and a time for today, since the
|
|
15
|
+
question a list answers is which of these is recent rather than exactly when each arrived.
|
|
16
|
+
|
|
17
|
+
Beside it, the same filter and sort the public page has: by name, infohash or category, ordered
|
|
18
|
+
by newest added, oldest added, name or size โ and by download speed, upload speed or share
|
|
19
|
+
ratio, which are read off the live status the poll refreshes, so rows reorder themselves under
|
|
20
|
+
those every few seconds. That is what a torrent client does and what choosing "download speed"
|
|
21
|
+
asks for, and also why none of them is the default. Newest added is, because a list read
|
|
22
|
+
straight after adding something should have that thing at the top.
|
|
23
|
+
|
|
24
|
+
Both happen inside the render rather than where the data arrives. The list refreshes every three
|
|
25
|
+
seconds; filtering at the fetch would either clear what had been typed on each poll or refetch
|
|
26
|
+
the whole catalog on every keystroke. And the count beside the box says `4 of 37` while a filter
|
|
27
|
+
is narrowing things, because an empty table and a table filtered down to nothing look identical.
|
|
28
|
+
|
|
29
|
+
### ๐ Bug fixes
|
|
30
|
+
|
|
10
31
|
## 0.27.0
|
|
11
32
|
### โจ Features and improvements
|
|
12
33
|
- **Bandwidth history per archive, kept across restarts.** The tile side of this question already
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pmtiles-swarm",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.28.0",
|
|
4
4
|
"description": "BitTorrent distribution for PMTiles map archives: create torrents, watch folders, publish and subscribe to RSS feeds, and seed through qBittorrent or an embedded client",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
package/src/web/index.html
CHANGED
|
@@ -324,12 +324,36 @@
|
|
|
324
324
|
<button type="button">Feed</button>
|
|
325
325
|
</a>
|
|
326
326
|
</div>
|
|
327
|
+
<div class="bar">
|
|
328
|
+
<input
|
|
329
|
+
id="archive-filter"
|
|
330
|
+
type="search"
|
|
331
|
+
placeholder="Filter by name, infohash or category"
|
|
332
|
+
autocomplete="off"
|
|
333
|
+
spellcheck="false"
|
|
334
|
+
style="flex:1;min-width:12rem"
|
|
335
|
+
/>
|
|
336
|
+
<label>
|
|
337
|
+
Sort
|
|
338
|
+
<select id="archive-sort">
|
|
339
|
+
<option value="added">newest added</option>
|
|
340
|
+
<option value="oldest">oldest added</option>
|
|
341
|
+
<option value="name">name</option>
|
|
342
|
+
<option value="largest">largest</option>
|
|
343
|
+
<option value="down">download speed</option>
|
|
344
|
+
<option value="up">upload speed</option>
|
|
345
|
+
<option value="ratio">share ratio</option>
|
|
346
|
+
</select>
|
|
347
|
+
</label>
|
|
348
|
+
<span class="sub" id="archive-count"></span>
|
|
349
|
+
</div>
|
|
327
350
|
|
|
328
351
|
<table>
|
|
329
352
|
<thead>
|
|
330
353
|
<tr>
|
|
331
354
|
<th>Archive</th>
|
|
332
355
|
<th>Size</th>
|
|
356
|
+
<th>Added</th>
|
|
333
357
|
<th>Mode</th>
|
|
334
358
|
<th>Origin</th>
|
|
335
359
|
<th>Progress</th>
|
|
@@ -710,6 +734,56 @@
|
|
|
710
734
|
const rate = (n) => (n > 0 ? `${bytes(n)}/s` : 'โ');
|
|
711
735
|
const pct = (n) => `${Math.round((n ?? 0) * 100)}%`;
|
|
712
736
|
|
|
737
|
+
/**
|
|
738
|
+
* When an archive was added, as something short enough for a column.
|
|
739
|
+
*
|
|
740
|
+
* A date on its own for anything older than today, and a time for
|
|
741
|
+
* today: the question a list answers is "which of these is recent", and
|
|
742
|
+
* a full timestamp on every row is harder to scan than either.
|
|
743
|
+
* @param {string} value - ISO timestamp.
|
|
744
|
+
* @returns {string} - Markup for the cell.
|
|
745
|
+
*/
|
|
746
|
+
const added = (value) => {
|
|
747
|
+
if (!value) return '<span class="sub">โ</span>';
|
|
748
|
+
const when = new Date(value);
|
|
749
|
+
if (Number.isNaN(when.getTime())) return '<span class="sub">โ</span>';
|
|
750
|
+
const today = new Date().toDateString() === when.toDateString();
|
|
751
|
+
return `<span title="${escapeHtml(when.toLocaleString())}">${
|
|
752
|
+
today
|
|
753
|
+
? when.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
|
|
754
|
+
: when.toLocaleDateString()
|
|
755
|
+
}</span>`;
|
|
756
|
+
};
|
|
757
|
+
|
|
758
|
+
// The same three questions the public page asks, against an admin row.
|
|
759
|
+
// Newest first by default: a list read after adding something is a list
|
|
760
|
+
// where the thing just added should be at the top.
|
|
761
|
+
/**
|
|
762
|
+
* A live figure off an archive's status, or zero.
|
|
763
|
+
*
|
|
764
|
+
* An archive the engine has not reported on yet has no status at all,
|
|
765
|
+
* and comparing undefined gives NaN -- which makes a sort silently do
|
|
766
|
+
* nothing rather than fail, so it is worth the guard.
|
|
767
|
+
* @param {object} entry - Catalog entry with its status attached.
|
|
768
|
+
* @param {string} field - Which figure.
|
|
769
|
+
* @returns {number} - The value, or zero.
|
|
770
|
+
*/
|
|
771
|
+
const speedOf = (entry, field) => Number(entry?.status?.[field] ?? 0) || 0;
|
|
772
|
+
|
|
773
|
+
const archiveSorters = {
|
|
774
|
+
added: (a, b) => new Date(b.createdAt ?? 0) - new Date(a.createdAt ?? 0),
|
|
775
|
+
oldest: (a, b) => new Date(a.createdAt ?? 0) - new Date(b.createdAt ?? 0),
|
|
776
|
+
name: (a, b) => String(a.name ?? '').localeCompare(String(b.name ?? '')),
|
|
777
|
+
largest: (a, b) => (b.size ?? 0) - (a.size ?? 0),
|
|
778
|
+
// Live values, read off the status the poll refreshes. Rows reorder
|
|
779
|
+
// themselves every few seconds under these, which is what a torrent
|
|
780
|
+
// client does and what somebody choosing "download speed" is asking
|
|
781
|
+
// for -- but it is also why they are not the default.
|
|
782
|
+
down: (a, b) => speedOf(b, 'downloadSpeed') - speedOf(a, 'downloadSpeed'),
|
|
783
|
+
up: (a, b) => speedOf(b, 'uploadSpeed') - speedOf(a, 'uploadSpeed'),
|
|
784
|
+
ratio: (a, b) => speedOf(b, 'ratio') - speedOf(a, 'ratio'),
|
|
785
|
+
};
|
|
786
|
+
|
|
713
787
|
let toastTimer;
|
|
714
788
|
const toast = (message) => {
|
|
715
789
|
$('toast').textContent = message;
|
|
@@ -1004,9 +1078,34 @@
|
|
|
1004
1078
|
function renderRows() {
|
|
1005
1079
|
const rows = $('rows');
|
|
1006
1080
|
rows.innerHTML = '';
|
|
1081
|
+
|
|
1082
|
+
// Filtered and sorted here rather than where the data arrives, so the
|
|
1083
|
+
// three-second poll keeps refreshing what is on screen without
|
|
1084
|
+
// resetting what was typed. `archives` stays the node's answer; this
|
|
1085
|
+
// is only the view of it.
|
|
1086
|
+
const needle = ($('archive-filter')?.value ?? '').trim().toLowerCase();
|
|
1087
|
+
const order = $('archive-sort')?.value ?? 'added';
|
|
1088
|
+
const shown = archives
|
|
1089
|
+
.filter(
|
|
1090
|
+
(entry) =>
|
|
1091
|
+
needle === '' ||
|
|
1092
|
+
String(entry.name ?? '').toLowerCase().includes(needle) ||
|
|
1093
|
+
String(entry.infoHash ?? '').toLowerCase().includes(needle) ||
|
|
1094
|
+
(entry.categories ?? []).some((tag) =>
|
|
1095
|
+
String(tag).toLowerCase().includes(needle),
|
|
1096
|
+
),
|
|
1097
|
+
)
|
|
1098
|
+
.sort(archiveSorters[order] ?? archiveSorters.added);
|
|
1099
|
+
|
|
1007
1100
|
$('empty').hidden = archives.length > 0;
|
|
1101
|
+
// Says when a filter is hiding things, because an empty table and a
|
|
1102
|
+
// table filtered down to nothing look identical otherwise.
|
|
1103
|
+
$('archive-count').textContent =
|
|
1104
|
+
needle === ''
|
|
1105
|
+
? ''
|
|
1106
|
+
: `${shown.length} of ${archives.length}`;
|
|
1008
1107
|
|
|
1009
|
-
for (const entry of
|
|
1108
|
+
for (const entry of shown) {
|
|
1010
1109
|
const s = entry.status ?? {};
|
|
1011
1110
|
const progress = s.progress ?? 0;
|
|
1012
1111
|
const mode = entry.mode ?? 'mirror';
|
|
@@ -1024,6 +1123,7 @@
|
|
|
1024
1123
|
? `<div class="sub">${escapeHtml(entry.kind)} ยท not servable</div>`
|
|
1025
1124
|
: ''
|
|
1026
1125
|
}</td>
|
|
1126
|
+
<td>${added(entry.createdAt)}</td>
|
|
1027
1127
|
<td><span class="pill ${mode}">${mode}</span></td>
|
|
1028
1128
|
<td>${originCell(entry)}</td>
|
|
1029
1129
|
<td>
|
|
@@ -4204,6 +4304,8 @@
|
|
|
4204
4304
|
$('traffic-live').onchange = trafficPoll;
|
|
4205
4305
|
trafficPoll();
|
|
4206
4306
|
|
|
4307
|
+
$('archive-filter').addEventListener('input', renderRows);
|
|
4308
|
+
$('archive-sort').addEventListener('change', renderRows);
|
|
4207
4309
|
$('traffic-window').onchange = () => loadSwarmTraffic();
|
|
4208
4310
|
$('traffic-reset').onclick = async () => {
|
|
4209
4311
|
// Deliberately separate from reading: a page polling the endpoint must
|