querysub 0.549.0 → 0.550.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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "querysub",
3
- "version": "0.549.0",
3
+ "version": "0.550.0",
4
4
  "main": "index.js",
5
5
  "license": "MIT",
6
6
  "note1": "note on node-forge fork, see https://github.com/digitalbazaar/forge/issues/744 for details",
@@ -71,7 +71,7 @@
71
71
  "node-forge": "https://github.com/sliftist/forge#e618181b469b07bdc70b968b0391beb8ef5fecd6",
72
72
  "pako": "^2.1.0",
73
73
  "peggy": "^5.0.6",
74
- "sliftutils": "^1.7.35",
74
+ "sliftutils": "^1.7.36",
75
75
  "socket-function": "^1.2.26",
76
76
  "terser": "^5.31.0",
77
77
  "typenode": "^6.6.1",
@@ -4,14 +4,13 @@ import { css } from "typesafecss";
4
4
  import { sort } from "socket-function/src/misc";
5
5
  import { formatNumber, formatDateTime, formatTime } from "socket-function/src/formatting/format";
6
6
  import { parseHostedUrl, parseBackblazeUrl } from "sliftutils/storage/remoteStorage/remoteConfig";
7
- import { getServerActiveBucket } from "sliftutils/storage/remoteStorage/createArchives";
8
- import { STORAGE_ACCOUNT } from "../../-a-archives/archives2";
7
+ import { SocketFunction } from "socket-function/SocketFunction";
9
8
  import { FULL_VALID_WINDOW, FULL_ROUTE } from "sliftutils/storage/IArchives";
10
9
  import type { RemoteConfig, RemoteConfigBase, HostedConfig, BackblazeConfig } from "sliftutils/storage/IArchives";
11
10
  import { showModal } from "../../5-diagnostics/Modal";
12
11
  import { FullscreenModal } from "../../5-diagnostics/FullscreenModal";
13
12
  import { Button } from "../../library-components/Button";
14
- import type { StorageServerBuckets } from "./StoragePage";
13
+ import { StorageSynced, type StorageServerBuckets } from "./StoragePage";
15
14
 
16
15
  module.hotreload = true;
17
16
 
@@ -250,24 +249,32 @@ async function refreshWatch(key: string): Promise<void> {
250
249
  let current = liveWatchData().watches[key];
251
250
  if (current) current.loading = true;
252
251
  });
253
- let live = await getServerActiveBucket({ url: serverUrl, account: STORAGE_ACCOUNT, bucketName });
254
- // "Not loaded here" comes back as a string - it's a normal result, not an error
255
- let error: string | undefined;
256
- let result: RemoteConfig | undefined;
257
- if (typeof live === "string") {
258
- error = live;
259
- } else {
260
- result = live.routing;
252
+ try {
253
+ // Proxied through our server, which holds the identity the storage servers authenticate against
254
+ let live = await StorageSynced(SocketFunction.browserNodeId()).getActiveBucket.promise(serverUrl, bucketName);
255
+ // "Not loaded here" comes back as a string - it's a normal result, not an error
256
+ let error: string | undefined;
257
+ let result: RemoteConfig | undefined;
258
+ if (typeof live === "string") {
259
+ error = live;
260
+ } else {
261
+ result = live.routing;
262
+ }
263
+ Querysub.commit(() => {
264
+ let current = liveWatchData().watches[key];
265
+ // The watch may have been removed while the call was in flight
266
+ if (!current) return;
267
+ current.routing = result;
268
+ current.error = error;
269
+ current.updatedTime = Date.now();
270
+ });
271
+ } finally {
272
+ // Without this a throw would leave the watch stuck showing "refreshing..."
273
+ Querysub.commit(() => {
274
+ let current = liveWatchData().watches[key];
275
+ if (current) current.loading = false;
276
+ });
261
277
  }
262
- Querysub.commit(() => {
263
- let current = liveWatchData().watches[key];
264
- // The watch may have been removed while the call was in flight
265
- if (!current) return;
266
- current.loading = false;
267
- current.routing = result;
268
- current.error = error;
269
- current.updatedTime = Date.now();
270
- });
271
278
  }
272
279
 
273
280
  function isWatched(serverUrl: string, bucketName: string): boolean {
@@ -5,8 +5,8 @@ import { formatNumber, formatTime, formatDateTimeDetailed } from "socket-functio
5
5
  import { sort, timeInSecond } from "socket-function/src/misc";
6
6
  import preact from "preact";
7
7
  import { Querysub } from "../../4-querysub/Querysub";
8
- import { listServerBuckets, clearServerWriteStats, activateServerBucket } from "sliftutils/storage/remoteStorage/createArchives";
9
- import type { ServerBucketInfo, BucketDiskInfo, BucketWriteStats } from "sliftutils/storage/remoteStorage/storageServerState";
8
+ import { listServerBuckets, clearServerWriteStats, activateServerBucket, getServerActiveBucket } from "sliftutils/storage/remoteStorage/createArchives";
9
+ import type { ServerBucketInfo, BucketDiskInfo, BucketWriteStats, ActiveBucketInfo } from "sliftutils/storage/remoteStorage/storageServerState";
10
10
  import type { ArchivesConfig } from "sliftutils/storage/IArchives";
11
11
  import { UsageBar, getUsageThresholds } from "../../library-components/UsageBar";
12
12
  import { getSyncedController } from "../../library-components/SyncedController";
@@ -86,11 +86,21 @@ export function getStorageServices(servers: StorageServer[]): StorageService[] {
86
86
  return [...byKey.values()];
87
87
  }
88
88
 
89
+ /** Every storage call is proxied through here: they authenticate with this machine's identity CA, which the browser has no access to. */
89
90
  class StoragePageControllerBase {
90
- /** Purely a forwarder - browsers can't authenticate to a storage server themselves. One call per server, so each caches and refreshes on its own. */
91
+ /** One call per server, so each caches and refreshes on its own. */
91
92
  public async getServerBuckets(url: string): Promise<ServerBucketInfo[]> {
92
93
  return await listServerBuckets({ url, account: STORAGE_ACCOUNT });
93
94
  }
95
+ public async activateBucket(url: string, bucketName: string): Promise<ActiveBucketInfo | string> {
96
+ return await activateServerBucket({ url, account: STORAGE_ACCOUNT, bucketName });
97
+ }
98
+ public async getActiveBucket(url: string, bucketName: string): Promise<ActiveBucketInfo | string> {
99
+ return await getServerActiveBucket({ url, account: STORAGE_ACCOUNT, bucketName });
100
+ }
101
+ public async clearWriteStats(url: string): Promise<{ clearedBuckets: number }> {
102
+ return await clearServerWriteStats({ url, account: STORAGE_ACCOUNT });
103
+ }
94
104
  }
95
105
 
96
106
  export const StoragePageController = SocketFunction.register(
@@ -98,6 +108,9 @@ export const StoragePageController = SocketFunction.register(
98
108
  new StoragePageControllerBase(),
99
109
  () => ({
100
110
  getServerBuckets: {},
111
+ activateBucket: {},
112
+ getActiveBucket: {},
113
+ clearWriteStats: {},
101
114
  }),
102
115
  () => ({
103
116
  hooks: [assertIsManagementUser],
@@ -106,7 +119,7 @@ export const StoragePageController = SocketFunction.register(
106
119
  }
107
120
  );
108
121
 
109
- const StorageSynced = getSyncedController(StoragePageController, {
122
+ export const StorageSynced = getSyncedController(StoragePageController, {
110
123
  reads: {},
111
124
  writes: {},
112
125
  });
@@ -219,22 +232,28 @@ class ActivateBucketButton extends qreact.Component<{ serverUrl: string; bucketN
219
232
  this.state.activating = true;
220
233
  this.state.error = "";
221
234
  Querysub.onCommitFinished(async () => {
222
- let result = await activateServerBucket({ url: serverUrl, account: STORAGE_ACCOUNT, bucketName });
223
- // A refusal comes back as a string - it's a normal result, not an error
224
- let refusal = typeof result === "string" && result || "";
225
- if (refusal) {
226
- console.log(`Activating bucket ${bucketName} on ${serverUrl} was refused: ${refusal}`);
227
- } else {
228
- console.log(`Activated bucket ${bucketName} on ${serverUrl}`);
229
- }
230
- Querysub.commit(() => {
231
- this.state.activating = false;
232
- this.state.error = refusal;
233
- // Only this server's buckets changed
234
- if (!refusal) {
235
- StorageSynced(SocketFunction.browserNodeId()).getServerBuckets.reset(serverUrl);
235
+ try {
236
+ let result = await StorageSynced(SocketFunction.browserNodeId()).activateBucket.promise(serverUrl, bucketName);
237
+ // A refusal comes back as a string - it's a normal result, not an error
238
+ let refusal = typeof result === "string" && result || "";
239
+ if (refusal) {
240
+ console.log(`Activating bucket ${bucketName} on ${serverUrl} was refused: ${refusal}`);
241
+ } else {
242
+ console.log(`Activated bucket ${bucketName} on ${serverUrl}`);
236
243
  }
237
- });
244
+ Querysub.commit(() => {
245
+ this.state.error = refusal;
246
+ // Only this server's buckets changed
247
+ if (!refusal) {
248
+ StorageSynced(SocketFunction.browserNodeId()).getServerBuckets.reset(serverUrl);
249
+ }
250
+ });
251
+ } finally {
252
+ // Without this a throw would leave the button disabled and stuck on "Activating..."
253
+ Querysub.commit(() => {
254
+ this.state.activating = false;
255
+ });
256
+ }
238
257
  });
239
258
  }}
240
259
  >
@@ -403,7 +422,7 @@ class DeployNotices extends qreact.Component<{ services: StorageService[] }> {
403
422
  async function resetWriteStats(servers: StorageServerBuckets[]): Promise<void> {
404
423
  await Promise.all(servers.map(async server => {
405
424
  if (server.loading) return;
406
- let { clearedBuckets } = await clearServerWriteStats({ url: server.url, account: STORAGE_ACCOUNT });
425
+ let { clearedBuckets } = await StorageSynced(SocketFunction.browserNodeId()).clearWriteStats.promise(server.url);
407
426
  console.log(`Cleared write stats for ${clearedBuckets} buckets on ${server.url}`);
408
427
  }));
409
428
  Querysub.commit(() => {