querysub 0.565.0 → 0.567.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/package.json
CHANGED
|
@@ -19,6 +19,9 @@ function createSourceWindows(
|
|
|
19
19
|
let timeRanges: [number, number][] = [];
|
|
20
20
|
for (let windowIndex = 0; windowIndex < sourceWindows.length; windowIndex++) {
|
|
21
21
|
let startTime = sourceWindows[windowIndex].startTime;
|
|
22
|
+
if (windowIndex === 0) {
|
|
23
|
+
startTime = 0;
|
|
24
|
+
}
|
|
22
25
|
let endTime = sourceWindows[windowIndex + 1]?.startTime ?? Number.MAX_SAFE_INTEGER;
|
|
23
26
|
if (startTime > endTime) throw new Error(`Source window ${formatDateTime(startTime)} starts after end of ${formatDateTime(endTime)}`);
|
|
24
27
|
timeRanges.push([startTime, endTime]);
|
|
@@ -40,29 +43,29 @@ function archiveBuilder(bucket: string, overrides: Partial<RemoteConfigBase>) {
|
|
|
40
43
|
const BACKBLAZE = `https://f002.backblazeb2.com/file/${bucket}/storage/storagerouting.json`;
|
|
41
44
|
let sources = createSourceWindows(overrides, [
|
|
42
45
|
{
|
|
43
|
-
startTime:
|
|
46
|
+
startTime: +new Date("2026-07-20 18:00:00-04:00"),
|
|
44
47
|
sources: [
|
|
45
|
-
{ type: "remote", url: ONTARIO, route: [0,
|
|
46
|
-
{ type: "remote", url: HETZNER, route: [0.5
|
|
48
|
+
{ type: "remote", url: ONTARIO, route: [0.5, 1], },
|
|
49
|
+
{ type: "remote", url: HETZNER, route: [0, 0.5], },
|
|
47
50
|
{ type: "remote", url: ONTARIO, },
|
|
48
51
|
{ type: "remote", url: HETZNER, },
|
|
49
52
|
{ type: "backblaze", url: BACKBLAZE, },
|
|
50
53
|
]
|
|
51
54
|
},
|
|
52
55
|
{
|
|
53
|
-
startTime: +new Date("2026-07-
|
|
56
|
+
startTime: +new Date("2026-07-21 02:59:00-04:00"),
|
|
54
57
|
sources: [
|
|
55
|
-
{ type: "remote", url: ONTARIO, route: [0.5
|
|
56
|
-
{ type: "remote", url: HETZNER, route: [0,
|
|
58
|
+
{ type: "remote", url: ONTARIO, route: [0, 0.5], },
|
|
59
|
+
{ type: "remote", url: HETZNER, route: [0.5, 1], },
|
|
57
60
|
{ type: "remote", url: ONTARIO, },
|
|
58
61
|
{ type: "remote", url: HETZNER, },
|
|
59
62
|
{ type: "backblaze", url: BACKBLAZE, },
|
|
60
63
|
]
|
|
61
|
-
}
|
|
64
|
+
},
|
|
62
65
|
]);
|
|
63
66
|
|
|
64
67
|
return createArchives({
|
|
65
|
-
version:
|
|
68
|
+
version: 15,
|
|
66
69
|
sources,
|
|
67
70
|
});
|
|
68
71
|
}
|
|
@@ -12,7 +12,7 @@ import type { RemoteConfig, RemoteConfigBase, HostedConfig, BackblazeConfig } fr
|
|
|
12
12
|
import { showModal } from "../../5-diagnostics/Modal";
|
|
13
13
|
import { FullscreenModal } from "../../5-diagnostics/FullscreenModal";
|
|
14
14
|
import { Button } from "../../library-components/Button";
|
|
15
|
-
import { Tag, TAG_FONT_SIZE, TAG_WARNING_COLOR,
|
|
15
|
+
import { Tag, TAG_FONT_SIZE, TAG_WARNING_COLOR, getSourceTags } from "./Tag";
|
|
16
16
|
import { StorageSynced, type StorageServerBuckets } from "./StoragePage";
|
|
17
17
|
|
|
18
18
|
module.hotreload = true;
|
|
@@ -343,21 +343,6 @@ function showConfigModal(bucketName: string, rawConfig: RemoteConfig): void {
|
|
|
343
343
|
});
|
|
344
344
|
}
|
|
345
345
|
|
|
346
|
-
function getSourceTags(source: Source): { icon: string; text: string }[] {
|
|
347
|
-
let tags: { icon: string; text: string }[] = [];
|
|
348
|
-
if (source.public) tags.push({ icon: "🌐", text: "public" });
|
|
349
|
-
if (source.immutable) tags.push({ icon: "🔒", text: "immutable" });
|
|
350
|
-
if (source.noFullSync) tags.push(NO_FULL_SYNC_TAG);
|
|
351
|
-
if (source.intermediate) tags.push({ icon: "⏳", text: "intermediate" });
|
|
352
|
-
if (source.type === "remote") {
|
|
353
|
-
if (source.fast) tags.push({ icon: "⚡", text: "fast" });
|
|
354
|
-
if (source.rawDisk) tags.push({ icon: "💽", text: "rawDisk" });
|
|
355
|
-
if (source.writeDelay !== undefined) tags.push({ icon: "⏱", text: `writeDelay ${formatTime(source.writeDelay)}` });
|
|
356
|
-
}
|
|
357
|
-
if (source.readerDiskLimit !== undefined) tags.push({ icon: "📦", text: `readCache ${formatNumber(source.readerDiskLimit)}B` });
|
|
358
|
-
return tags;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
346
|
/** Watching a source pins that server's own live view of the bucket to the top of the page, so disagreements between servers are visible directly. */
|
|
362
347
|
class WatchButton extends qreact.Component<{ source: Source; bucketName: string }> {
|
|
363
348
|
render() {
|
|
@@ -17,7 +17,7 @@ import { isDefined } from "../../misc";
|
|
|
17
17
|
import { STORAGE_ACCOUNT } from "../../-a-archives/archives2";
|
|
18
18
|
import { Table } from "../../5-diagnostics/Table";
|
|
19
19
|
import { RouteConfigView } from "./RouteConfigView";
|
|
20
|
-
import { Tag,
|
|
20
|
+
import { Tag, SourceTag, getSourceTags } from "./Tag";
|
|
21
21
|
|
|
22
22
|
module.hotreload = true;
|
|
23
23
|
|
|
@@ -134,7 +134,7 @@ type BucketRow = {
|
|
|
134
134
|
state: string;
|
|
135
135
|
files: string;
|
|
136
136
|
bytes: string;
|
|
137
|
-
|
|
137
|
+
flags: SourceTag[];
|
|
138
138
|
indexSources: ArchivesConfig["indexSources"];
|
|
139
139
|
readerDiskLimit: string;
|
|
140
140
|
writes: string;
|
|
@@ -152,13 +152,13 @@ const DISK_BAR_TYPE = "DISK";
|
|
|
152
152
|
const ACTIVE_STATE = "active";
|
|
153
153
|
const INACTIVE_STATE = "inactive";
|
|
154
154
|
|
|
155
|
-
/**
|
|
156
|
-
function
|
|
155
|
+
/** The flags this server's own entries in the bucket's routing config set - how it holds the bucket, which is not something the bucket's own state reports. */
|
|
156
|
+
function getBucketFlags(serverUrl: string, bucketName: string, remoteConfig: RemoteConfig | undefined): SourceTag[] {
|
|
157
157
|
let host: URL;
|
|
158
158
|
try {
|
|
159
159
|
host = new URL(serverUrl);
|
|
160
160
|
} catch {
|
|
161
|
-
return
|
|
161
|
+
return [];
|
|
162
162
|
}
|
|
163
163
|
let selfEntries: HostedConfig[] = [];
|
|
164
164
|
for (let source of remoteConfig?.sources || []) {
|
|
@@ -173,7 +173,15 @@ function getNoFullSync(serverUrl: string, bucketName: string, remoteConfig: Remo
|
|
|
173
173
|
// A malformed url in the config is not this column's problem
|
|
174
174
|
}
|
|
175
175
|
}
|
|
176
|
-
|
|
176
|
+
// A server can hold several entries for one bucket (different windows or route shards), so the flags are the union of theirs, deduped by what they say
|
|
177
|
+
let flags = new Map<string, SourceTag>();
|
|
178
|
+
for (let entry of selfEntries) {
|
|
179
|
+
for (let tag of getSourceTags(entry)) {
|
|
180
|
+
if (flags.has(tag.text)) continue;
|
|
181
|
+
flags.set(tag.text, tag);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return [...flags.values()];
|
|
177
185
|
}
|
|
178
186
|
|
|
179
187
|
/** How much fast-mode coalescing saved: every accepted write over the ones that actually reached a source. */
|
|
@@ -195,7 +203,7 @@ function getBucketRows(servers: StorageServerBuckets[]): BucketRow[] {
|
|
|
195
203
|
state: "",
|
|
196
204
|
files: "",
|
|
197
205
|
bytes: "",
|
|
198
|
-
|
|
206
|
+
flags: [],
|
|
199
207
|
indexSources: undefined,
|
|
200
208
|
readerDiskLimit: "",
|
|
201
209
|
writes: "",
|
|
@@ -225,7 +233,7 @@ function getBucketRows(servers: StorageServerBuckets[]): BucketRow[] {
|
|
|
225
233
|
state: bucket.active && ACTIVE_STATE || INACTIVE_STATE,
|
|
226
234
|
files: config?.index && formatNumber(config.index.fileCount) || "",
|
|
227
235
|
bytes: config?.index && formatNumber(config.index.byteCount) + "B" || "",
|
|
228
|
-
|
|
236
|
+
flags: getBucketFlags(server.url, bucket.bucketName, config?.remoteConfig),
|
|
229
237
|
indexSources: config?.indexSources,
|
|
230
238
|
readerDiskLimit: config?.readerDiskLimit && formatNumber(config.readerDiskLimit) + "B" || "",
|
|
231
239
|
writes: bucket.writeStats && formatNumber(bucket.writeStats.originalWrites) || "",
|
|
@@ -581,10 +589,12 @@ export class StoragePage extends qreact.Component {
|
|
|
581
589
|
},
|
|
582
590
|
files: { title: "Files" },
|
|
583
591
|
bytes: { title: "Bytes" },
|
|
584
|
-
|
|
585
|
-
title: "
|
|
586
|
-
//
|
|
587
|
-
formatter:
|
|
592
|
+
flags: {
|
|
593
|
+
title: "Flags",
|
|
594
|
+
// Only flags that are set produce a tag, so an empty cell means every default
|
|
595
|
+
formatter: flags => <div className={css.hbox(4).wrap}>
|
|
596
|
+
{(flags || []).map(tag => <Tag key={tag.text} icon={tag.icon} text={tag.text} />)}
|
|
597
|
+
</div>
|
|
588
598
|
},
|
|
589
599
|
indexSources: {
|
|
590
600
|
title: "Index sources",
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { qreact } from "../../4-dom/qreact";
|
|
2
2
|
import { css } from "typesafecss";
|
|
3
|
+
import { formatNumber, formatTime } from "socket-function/src/formatting/format";
|
|
4
|
+
import type { HostedConfig, BackblazeConfig } from "sliftutils/storage/IArchives";
|
|
3
5
|
|
|
4
6
|
module.hotreload = true;
|
|
5
7
|
|
|
@@ -9,8 +11,23 @@ export const TAG_COLOR: TagColor = { h: 210, s: 45, l: 88 };
|
|
|
9
11
|
export const TAG_WARNING_COLOR: TagColor = { h: 35, s: 90, l: 85 };
|
|
10
12
|
export const TAG_FONT_SIZE = 11;
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
+
export type SourceTag = { icon: string; text: string };
|
|
15
|
+
|
|
16
|
+
/** Every flag a source has set. Only flags that are on produce a tag - an absent tag is the default, so there is nothing to show for it. */
|
|
17
|
+
export function getSourceTags(source: HostedConfig | BackblazeConfig): SourceTag[] {
|
|
18
|
+
let tags: SourceTag[] = [];
|
|
19
|
+
if (source.public) tags.push({ icon: "🌐", text: "public" });
|
|
20
|
+
if (source.immutable) tags.push({ icon: "🔒", text: "immutable" });
|
|
21
|
+
if (source.noFullSync) tags.push({ icon: "⇣", text: "noFullSync" });
|
|
22
|
+
if (source.intermediate) tags.push({ icon: "⏳", text: "intermediate" });
|
|
23
|
+
if (source.type === "remote") {
|
|
24
|
+
if (source.fast) tags.push({ icon: "⚡", text: "fast" });
|
|
25
|
+
if (source.rawDisk) tags.push({ icon: "💽", text: "rawDisk" });
|
|
26
|
+
if (source.writeDelay !== undefined) tags.push({ icon: "⏱", text: `writeDelay ${formatTime(source.writeDelay)}` });
|
|
27
|
+
}
|
|
28
|
+
if (source.readerDiskLimit !== undefined) tags.push({ icon: "📦", text: `readCache ${formatNumber(source.readerDiskLimit)}B` });
|
|
29
|
+
return tags;
|
|
30
|
+
}
|
|
14
31
|
|
|
15
32
|
export class Tag extends qreact.Component<{ icon: string; text: string; warning?: boolean; color?: TagColor; title?: string; }> {
|
|
16
33
|
render() {
|