@truenas/api-client 1.0.13 → 2.0.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/README.md +87 -0
- package/dist/index.cjs +270 -200
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +12206 -13573
- package/dist/index.d.ts +12206 -13573
- package/dist/index.js +270 -201
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { map, toArray, switchMap, concat, from, defer, throwError, firstValueFrom, BehaviorSubject, Subject, distinctUntilChanged, of, filter, take, catchError as catchError$1, retry, startWith, shareReplay as shareReplay$1, takeUntil, EMPTY, combineLatest, interval, Observable, timeout, timer, race, tap, share, finalize, merge, takeWhile } from 'rxjs';
|
|
2
2
|
import { webSocket } from 'rxjs/webSocket';
|
|
3
3
|
import { map as map$1, catchError, shareReplay } from 'rxjs/operators';
|
|
4
4
|
|
|
@@ -8,76 +8,6 @@ var __export = (target, all) => {
|
|
|
8
8
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
-
// src/enums/truenas-endpoint.enum.ts
|
|
12
|
-
var TrueNasEndpoint = /* @__PURE__ */ ((TrueNasEndpoint2) => {
|
|
13
|
-
TrueNasEndpoint2["UpdateStatus"] = "update.status";
|
|
14
|
-
TrueNasEndpoint2["AppQuery"] = "app.query";
|
|
15
|
-
TrueNasEndpoint2["AppStart"] = "app.start";
|
|
16
|
-
TrueNasEndpoint2["AppStop"] = "app.stop";
|
|
17
|
-
TrueNasEndpoint2["AppRedeploy"] = "app.redeploy";
|
|
18
|
-
TrueNasEndpoint2["AppUpgrade"] = "app.upgrade";
|
|
19
|
-
TrueNasEndpoint2["CoreSubscribe"] = "core.subscribe";
|
|
20
|
-
TrueNasEndpoint2["ReplicationQuery"] = "replication.query";
|
|
21
|
-
TrueNasEndpoint2["ReplicationRun"] = "replication.run";
|
|
22
|
-
TrueNasEndpoint2["InterfaceQuery"] = "interface.query";
|
|
23
|
-
TrueNasEndpoint2["CloudBackupQuery"] = "cloud_backup.query";
|
|
24
|
-
TrueNasEndpoint2["CloudSyncQuery"] = "cloudsync.query";
|
|
25
|
-
TrueNasEndpoint2["VirtualInstanceQuery"] = "virt.instance.query";
|
|
26
|
-
TrueNasEndpoint2["VirtualInstanceStop"] = "virt.instance.stop";
|
|
27
|
-
TrueNasEndpoint2["VirtualInstanceStart"] = "virt.instance.start";
|
|
28
|
-
TrueNasEndpoint2["VirtualInstanceRestart"] = "virt.instance.restart";
|
|
29
|
-
TrueNasEndpoint2["ContainerQuery"] = "container.query";
|
|
30
|
-
TrueNasEndpoint2["ContainerStart"] = "container.start";
|
|
31
|
-
TrueNasEndpoint2["ContainerStop"] = "container.stop";
|
|
32
|
-
TrueNasEndpoint2["VmQuery"] = "vm.query";
|
|
33
|
-
TrueNasEndpoint2["VmStart"] = "vm.start";
|
|
34
|
-
TrueNasEndpoint2["VmStop"] = "vm.stop";
|
|
35
|
-
TrueNasEndpoint2["VmRestart"] = "vm.restart";
|
|
36
|
-
TrueNasEndpoint2["CoreDownload"] = "core.download";
|
|
37
|
-
TrueNasEndpoint2["CoreGetJobs"] = "core.get_jobs";
|
|
38
|
-
TrueNasEndpoint2["SystemInfo"] = "system.info";
|
|
39
|
-
TrueNasEndpoint2["SystemProductType"] = "system.product_type";
|
|
40
|
-
TrueNasEndpoint2["ExtendedSystemInfo"] = "webui.main.dashboard.sys_info";
|
|
41
|
-
TrueNasEndpoint2["DiskQuery"] = "disk.query";
|
|
42
|
-
TrueNasEndpoint2["DeviceGetInfo"] = "device.get_info";
|
|
43
|
-
TrueNasEndpoint2["CorePing"] = "core.ping";
|
|
44
|
-
TrueNasEndpoint2["GenerateToken"] = "auth.generate_token";
|
|
45
|
-
TrueNasEndpoint2["AlertList"] = "alert.list";
|
|
46
|
-
TrueNasEndpoint2["AuthLogin"] = "auth.login_ex";
|
|
47
|
-
TrueNasEndpoint2["AuthLogout"] = "auth.logout";
|
|
48
|
-
TrueNasEndpoint2["ApiKeyQuery"] = "api_key.query";
|
|
49
|
-
TrueNasEndpoint2["ApiKeyCreate"] = "api_key.create";
|
|
50
|
-
TrueNasEndpoint2["ApiKeyDelete"] = "api_key.delete";
|
|
51
|
-
TrueNasEndpoint2["FailOverStatus"] = "failover.status";
|
|
52
|
-
TrueNasEndpoint2["FailOverNode"] = "failover.node";
|
|
53
|
-
TrueNasEndpoint2["FailOverDisabledReasons"] = "failover.disabled.reasons";
|
|
54
|
-
TrueNasEndpoint2["PoolQuery"] = "pool.query";
|
|
55
|
-
TrueNasEndpoint2["DatasetQuery"] = "pool.dataset.query";
|
|
56
|
-
TrueNasEndpoint2["DatasetCreate"] = "pool.dataset.create";
|
|
57
|
-
TrueNasEndpoint2["DatasetDelete"] = "pool.dataset.delete";
|
|
58
|
-
TrueNasEndpoint2["SnapshotTaskCreate"] = "pool.snapshottask.create";
|
|
59
|
-
TrueNasEndpoint2["SnapshotTaskQuery"] = "pool.snapshottask.query";
|
|
60
|
-
TrueNasEndpoint2["SnapshotTaskDelete"] = "pool.snapshottask.delete";
|
|
61
|
-
TrueNasEndpoint2["SnapshotCreate"] = "pool.snapshot.create";
|
|
62
|
-
TrueNasEndpoint2["SnapshotDelete"] = "pool.snapshot.delete";
|
|
63
|
-
TrueNasEndpoint2["ReplicationCountEligibleSnapshots"] = "replication.count_eligible_manual_snapshots";
|
|
64
|
-
TrueNasEndpoint2["BootGetState"] = "boot.get_state";
|
|
65
|
-
TrueNasEndpoint2["TnConnectUpdate"] = "tn_connect.update";
|
|
66
|
-
TrueNasEndpoint2["HardwareVirtualizationVariant"] = "hardware.virtualization.variant";
|
|
67
|
-
TrueNasEndpoint2["KeychainCredentialGenerateSshKeyPair"] = "keychaincredential.generate_ssh_key_pair";
|
|
68
|
-
TrueNasEndpoint2["KeychainCredentialCreate"] = "keychaincredential.create";
|
|
69
|
-
TrueNasEndpoint2["KeychainCredentialRemoteSshHostKeyScan"] = "keychaincredential.remote_ssh_host_key_scan";
|
|
70
|
-
TrueNasEndpoint2["KeychainCredentialDelete"] = "keychaincredential.delete";
|
|
71
|
-
TrueNasEndpoint2["ReplicationCreate"] = "replication.create";
|
|
72
|
-
TrueNasEndpoint2["UserQuery"] = "user.query";
|
|
73
|
-
TrueNasEndpoint2["UserUpdate"] = "user.update";
|
|
74
|
-
TrueNasEndpoint2["NetworkConfiguration"] = "network.configuration.config";
|
|
75
|
-
TrueNasEndpoint2["ServiceQuery"] = "service.query";
|
|
76
|
-
TrueNasEndpoint2["ServiceUpdate"] = "service.update";
|
|
77
|
-
TrueNasEndpoint2["ServiceControl"] = "service.control";
|
|
78
|
-
return TrueNasEndpoint2;
|
|
79
|
-
})(TrueNasEndpoint || {});
|
|
80
|
-
|
|
81
11
|
// src/types/api-error.type.ts
|
|
82
12
|
function isJsonRpcError(error) {
|
|
83
13
|
return typeof error === "object" && error !== null && "message" in error && typeof error.message === "string";
|
|
@@ -112,6 +42,16 @@ var JobState = /* @__PURE__ */ ((JobState2) => {
|
|
|
112
42
|
JobState2["Waiting"] = "WAITING";
|
|
113
43
|
return JobState2;
|
|
114
44
|
})(JobState || {});
|
|
45
|
+
var terminalStates = [
|
|
46
|
+
"SUCCESS" /* Success */,
|
|
47
|
+
"FAILED" /* Failed */,
|
|
48
|
+
"ABORTED" /* Aborted */,
|
|
49
|
+
"ERROR" /* Error */,
|
|
50
|
+
"FINISHED" /* Finished */
|
|
51
|
+
];
|
|
52
|
+
function isJobFinished(job) {
|
|
53
|
+
return terminalStates.includes(job.state);
|
|
54
|
+
}
|
|
115
55
|
var withId = (id) => filter((msg) => msg.id === id);
|
|
116
56
|
function randomUUID() {
|
|
117
57
|
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
@@ -138,6 +78,7 @@ function createJsonRpcMessage(method, params) {
|
|
|
138
78
|
}
|
|
139
79
|
|
|
140
80
|
// src/api/truenas-api.ts
|
|
81
|
+
var EVENT_KINDS = ["added", "changed", "removed"];
|
|
141
82
|
var TrueNasApi = class {
|
|
142
83
|
constructor(authenticated, connection) {
|
|
143
84
|
this.authenticated = authenticated;
|
|
@@ -154,33 +95,67 @@ var TrueNasApi = class {
|
|
|
154
95
|
filter((job) => !!job?.id),
|
|
155
96
|
share()
|
|
156
97
|
);
|
|
98
|
+
/**
|
|
99
|
+
* One shared stream per subscribed event name — see {@link events}.
|
|
100
|
+
* Keyed by name rather than by caller, because the subscription it stands
|
|
101
|
+
* for lives on the server and is per socket, not per caller.
|
|
102
|
+
*/
|
|
103
|
+
this.eventStreams = /* @__PURE__ */ new Map();
|
|
157
104
|
this.initializeJobEventsSubscription();
|
|
158
105
|
}
|
|
159
|
-
|
|
160
|
-
|
|
106
|
+
/**
|
|
107
|
+
* Send a request to a method of the surface this instance is typed against,
|
|
108
|
+
* and emit its result.
|
|
109
|
+
*
|
|
110
|
+
* ```typescript
|
|
111
|
+
* api.call('system.info') // SystemInfo
|
|
112
|
+
* api.call('pool.dataset.delete', ['tank/ds', { recursive: true }])
|
|
113
|
+
* ```
|
|
114
|
+
*
|
|
115
|
+
* `params` is required exactly when the method takes them — the directory
|
|
116
|
+
* says which — so a method that needs an id cannot be called without one.
|
|
117
|
+
*
|
|
118
|
+
* The polymorphic `.query` methods are reachable here too, but their
|
|
119
|
+
* `response` is the five-way union the server may return, which is
|
|
120
|
+
* {@link query} / {@link queryOne} / {@link queryCount}'s job to resolve.
|
|
121
|
+
* Reach for a verb instead.
|
|
122
|
+
*/
|
|
123
|
+
call(method, ...params) {
|
|
124
|
+
return this.dispatch(method, params[0]);
|
|
161
125
|
}
|
|
162
126
|
/**
|
|
163
127
|
* Send a JSON-RPC request and emit its result.
|
|
164
128
|
*
|
|
165
|
-
* Shared by {@link call} and the query verbs, which
|
|
166
|
-
*
|
|
167
|
-
* the verbs
|
|
129
|
+
* Shared by {@link call} and the query verbs, which read the same directory
|
|
130
|
+
* but resolve different things from it: `call` takes the entry's `response`
|
|
131
|
+
* verbatim, the verbs narrow its polymorphic union by the verb chosen.
|
|
132
|
+
*
|
|
133
|
+
* Also the way the class reaches methods on its own behalf — `core.get_jobs`,
|
|
134
|
+
* `auth.generate_token`. Inside the class body `D` is a type parameter, so
|
|
135
|
+
* the checker cannot know those are among its methods; they are, since both
|
|
136
|
+
* are in the shared base every surface extends.
|
|
168
137
|
*/
|
|
169
138
|
dispatch(method, params) {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
139
|
+
return defer(() => {
|
|
140
|
+
const message = createJsonRpcMessage(method, params);
|
|
141
|
+
const messageId = message.id ?? "";
|
|
142
|
+
const reply = this.connection.messages().pipe(
|
|
143
|
+
withId(messageId),
|
|
144
|
+
map((msg) => {
|
|
145
|
+
if (msg.error) {
|
|
146
|
+
const errorMessage = getApiErrorMessage(
|
|
147
|
+
msg.error,
|
|
148
|
+
"API call failed"
|
|
149
|
+
);
|
|
150
|
+
throw new Error(errorMessage);
|
|
151
|
+
}
|
|
152
|
+
return msg.result;
|
|
153
|
+
}),
|
|
154
|
+
take(1)
|
|
155
|
+
);
|
|
156
|
+
const sending = this.connection.send(message);
|
|
157
|
+
return reply.pipe(finalize(() => sending.unsubscribe()));
|
|
158
|
+
});
|
|
184
159
|
}
|
|
185
160
|
/**
|
|
186
161
|
* Query a collection and emit the matching entries.
|
|
@@ -235,81 +210,159 @@ var TrueNasApi = class {
|
|
|
235
210
|
return this.dispatch(method, [filters ?? [], { count: true }]);
|
|
236
211
|
}
|
|
237
212
|
/**
|
|
238
|
-
*
|
|
239
|
-
*
|
|
213
|
+
* Start a job and emit its id.
|
|
214
|
+
*
|
|
215
|
+
* Keyed off the surface's *job* directory rather than its call directory —
|
|
216
|
+
* a disjoint key space, so `callAndGetJobId('app.query')` is rejected and
|
|
217
|
+
* `call('app.start')` is too. Which one a method belongs to is a fact about
|
|
218
|
+
* the method, and the generated directories are where that fact lives.
|
|
240
219
|
*
|
|
241
|
-
* The
|
|
242
|
-
* the
|
|
220
|
+
* The id comes from the first job event whose `message_ids` carries this
|
|
221
|
+
* request's id, not from the response: what a job method returns on the wire
|
|
222
|
+
* differs by version (v25.10 answers with the id, v26 with `null`), while
|
|
223
|
+
* the event correlation holds for both.
|
|
243
224
|
*
|
|
244
|
-
* @param method The API method to call
|
|
245
|
-
* @param params The parameters for the API call
|
|
246
225
|
* @returns Observable that emits the job ID when received from websocket events
|
|
247
226
|
*/
|
|
248
|
-
callAndGetJobId(method, params) {
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
events(eventName) {
|
|
259
|
-
this.authenticated.pipe(filter(Boolean), take(1)).subscribe(() => {
|
|
260
|
-
const message = createJsonRpcMessage("core.subscribe", [eventName]);
|
|
261
|
-
this.connection.ws.next(message);
|
|
227
|
+
callAndGetJobId(method, ...params) {
|
|
228
|
+
return defer(() => {
|
|
229
|
+
const message = createJsonRpcMessage(method, params[0]);
|
|
230
|
+
const seen = this.jobEvents.pipe(
|
|
231
|
+
filter((job) => job.message_ids?.includes(message.id ?? "") ?? false),
|
|
232
|
+
map((job) => job.id),
|
|
233
|
+
take(1)
|
|
234
|
+
);
|
|
235
|
+
const sending = this.connection.send(message);
|
|
236
|
+
return seen.pipe(finalize(() => sending.unsubscribe()));
|
|
262
237
|
});
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Start a job and follow it to completion.
|
|
241
|
+
*
|
|
242
|
+
* Emits the job's state as it progresses and completes when the job reaches
|
|
243
|
+
* a terminal state, so `last()` gives the finished job and the intermediate
|
|
244
|
+
* emissions drive a progress indicator.
|
|
245
|
+
*
|
|
246
|
+
* ```typescript
|
|
247
|
+
* api.job('pool.dataset.unlock', ['tank/enc', { … }])
|
|
248
|
+
* .subscribe(job => bar.set(job.progress.percent ?? 0));
|
|
249
|
+
* ```
|
|
250
|
+
*
|
|
251
|
+
* The result is typed from the job directory, which is the whole reason to
|
|
252
|
+
* prefer this over {@link callAndGetJobId} plus {@link trackJob}: those two
|
|
253
|
+
* lose the connection between the method and its result, and the job comes
|
|
254
|
+
* back with `result: unknown`. It is `R | null` either way — a job that has
|
|
255
|
+
* not finished has no result, and neither does one that failed.
|
|
256
|
+
*/
|
|
257
|
+
job(method, ...params) {
|
|
258
|
+
return this.callAndGetJobId(method, ...params).pipe(
|
|
259
|
+
switchMap((jobId) => this.trackJob(jobId))
|
|
268
260
|
);
|
|
269
261
|
}
|
|
262
|
+
/**
|
|
263
|
+
* Subscribe to a collection and emit its changes.
|
|
264
|
+
*
|
|
265
|
+
* Emits the change itself rather than the transport frame, as a union
|
|
266
|
+
* discriminated on `msg`:
|
|
267
|
+
*
|
|
268
|
+
* ```typescript
|
|
269
|
+
* api.events('app.query').subscribe(event => {
|
|
270
|
+
* if (event.msg === 'removed') return drop(event.id);
|
|
271
|
+
* render(event.fields); // only reachable once narrowed
|
|
272
|
+
* });
|
|
273
|
+
* ```
|
|
274
|
+
*
|
|
275
|
+
* The narrowing is load-bearing, not decoration: a `removed` event carries
|
|
276
|
+
* an `id` and no `fields` in 55 of the 56 collections that declare one, so
|
|
277
|
+
* reaching `fields` unconditionally is wrong for almost all of them.
|
|
278
|
+
*
|
|
279
|
+
* Event *sources* — the entries taking subscribe-time arguments — are not
|
|
280
|
+
* reachable here; see {@link EventName}.
|
|
281
|
+
*/
|
|
282
|
+
events(event) {
|
|
283
|
+
const existing = this.eventStreams.get(event);
|
|
284
|
+
if (existing) return existing;
|
|
285
|
+
const stream = defer(() => {
|
|
286
|
+
const resubscribe = this.authenticated.pipe(distinctUntilChanged(), filter(Boolean)).subscribe(() => {
|
|
287
|
+
this.connection.send(createJsonRpcMessage("core.subscribe", [event]));
|
|
288
|
+
});
|
|
289
|
+
return this.connection.messages().pipe(
|
|
290
|
+
filter((res) => res.method === "collection_update"),
|
|
291
|
+
map((res) => res.params),
|
|
292
|
+
filter(
|
|
293
|
+
(params) => params?.collection === event && EVENT_KINDS.includes(params.msg)
|
|
294
|
+
),
|
|
295
|
+
// `collection` is the subscription the caller already named; what is
|
|
296
|
+
// left is exactly the payload the directory declares, plus its tag.
|
|
297
|
+
map(({ collection, ...change }) => change),
|
|
298
|
+
finalize(() => resubscribe.unsubscribe())
|
|
299
|
+
);
|
|
300
|
+
}).pipe(share({ resetOnRefCountZero: false, resetOnComplete: false }));
|
|
301
|
+
this.eventStreams.set(event, stream);
|
|
302
|
+
return stream;
|
|
303
|
+
}
|
|
270
304
|
/**
|
|
271
305
|
* Convenience wrapper for auth.generate_token API call.
|
|
306
|
+
*
|
|
307
|
+
* Goes through {@link dispatch} rather than {@link call}: inside the class
|
|
308
|
+
* body `D` is still a type parameter, so TypeScript cannot know that
|
|
309
|
+
* `auth.generate_token` is one of its methods. It is — the method is in the
|
|
310
|
+
* shared base, so every surface has it — but proving that to the checker
|
|
311
|
+
* would mean constraining `D` on the class, which would push the constraint
|
|
312
|
+
* onto every caller. The signature below is the guarantee instead.
|
|
272
313
|
*/
|
|
273
314
|
generateToken(ttl = 600, matchOrigin = false, singleUse = true) {
|
|
274
|
-
return this.
|
|
315
|
+
return this.dispatch("auth.generate_token", [
|
|
275
316
|
ttl,
|
|
276
317
|
{},
|
|
277
318
|
matchOrigin,
|
|
278
319
|
singleUse
|
|
279
320
|
]);
|
|
280
321
|
}
|
|
322
|
+
/**
|
|
323
|
+
* Follow an already-started job to completion.
|
|
324
|
+
*
|
|
325
|
+
* @typeParam R - what the job resolves to. An id carries no evidence of
|
|
326
|
+
* which method produced it, so nothing here can infer this and the default
|
|
327
|
+
* is `unknown`; naming it is the caller's assertion. {@link job} knows the
|
|
328
|
+
* method and fills it in from the directory, which is the reason to prefer
|
|
329
|
+
* it whenever you are the one starting the job.
|
|
330
|
+
*/
|
|
281
331
|
trackJob(jobId) {
|
|
282
|
-
const
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
"FINISHED" /* Finished */
|
|
288
|
-
];
|
|
289
|
-
const currentJobState$ = this.call("core.get_jobs", [
|
|
332
|
+
const updates$ = this.jobEvents.pipe(
|
|
333
|
+
filter((job) => job.id === jobId),
|
|
334
|
+
map((job) => job)
|
|
335
|
+
);
|
|
336
|
+
const read$ = this.dispatch("core.get_jobs", [
|
|
290
337
|
[["id", "=", jobId]]
|
|
291
338
|
]).pipe(
|
|
292
339
|
map((jobs) => jobs[0]),
|
|
293
|
-
|
|
340
|
+
share()
|
|
294
341
|
);
|
|
295
|
-
const
|
|
296
|
-
filter((job) => job
|
|
297
|
-
|
|
298
|
-
// Include the final state
|
|
342
|
+
const missing$ = read$.pipe(
|
|
343
|
+
filter((job) => job === void 0),
|
|
344
|
+
takeUntil(updates$)
|
|
299
345
|
);
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
346
|
+
const snapshot$ = read$.pipe(
|
|
347
|
+
filter((job) => job !== void 0),
|
|
348
|
+
takeUntil(updates$)
|
|
349
|
+
);
|
|
350
|
+
return merge(snapshot$, updates$).pipe(
|
|
351
|
+
takeWhile((job) => !isJobFinished(job), true),
|
|
352
|
+
// Include the final state
|
|
353
|
+
takeUntil(missing$)
|
|
307
354
|
);
|
|
308
355
|
}
|
|
356
|
+
/**
|
|
357
|
+
* Ask the server for job events, and keep asking after every reconnect —
|
|
358
|
+
* `authenticated$` returns to `false` when the socket drops, and a `take(1)`
|
|
359
|
+
* here left job tracking permanently deaf once that happened.
|
|
360
|
+
*/
|
|
309
361
|
initializeJobEventsSubscription() {
|
|
310
|
-
this.authenticated.pipe(
|
|
311
|
-
|
|
312
|
-
|
|
362
|
+
this.authenticated.pipe(distinctUntilChanged(), filter(Boolean)).subscribe(() => {
|
|
363
|
+
this.connection.send(
|
|
364
|
+
createJsonRpcMessage("core.subscribe", ["core.get_jobs"])
|
|
365
|
+
);
|
|
313
366
|
});
|
|
314
367
|
}
|
|
315
368
|
};
|
|
@@ -3005,6 +3058,36 @@ var TrueNasApiClient = class {
|
|
|
3005
3058
|
}
|
|
3006
3059
|
};
|
|
3007
3060
|
|
|
3061
|
+
// src/types/app-query.type.ts
|
|
3062
|
+
var AppState = /* @__PURE__ */ ((AppState2) => {
|
|
3063
|
+
AppState2["Running"] = "RUNNING";
|
|
3064
|
+
AppState2["Stopped"] = "STOPPED";
|
|
3065
|
+
AppState2["Stopping"] = "STOPPING";
|
|
3066
|
+
AppState2["Deploying"] = "DEPLOYING";
|
|
3067
|
+
return AppState2;
|
|
3068
|
+
})(AppState || {});
|
|
3069
|
+
|
|
3070
|
+
// src/utils/app-state.utils.ts
|
|
3071
|
+
function toAppState(state) {
|
|
3072
|
+
switch (state.toUpperCase()) {
|
|
3073
|
+
case "RUNNING":
|
|
3074
|
+
return "RUNNING" /* Running */;
|
|
3075
|
+
case "STOPPED":
|
|
3076
|
+
return "STOPPED" /* Stopped */;
|
|
3077
|
+
case "STOPPING":
|
|
3078
|
+
return "STOPPING" /* Stopping */;
|
|
3079
|
+
// `STARTING` is v25.10 `virt.instance` only, and folding it into `Stopped`
|
|
3080
|
+
// told callers a container that is coming up is at rest — enough for a UI
|
|
3081
|
+
// to offer a Start button for it, or for a poll loop to give up. `Deploying`
|
|
3082
|
+
// is the only in-progress state `AppState` has.
|
|
3083
|
+
case "STARTING":
|
|
3084
|
+
case "DEPLOYING":
|
|
3085
|
+
return "DEPLOYING" /* Deploying */;
|
|
3086
|
+
default:
|
|
3087
|
+
return "STOPPED" /* Stopped */;
|
|
3088
|
+
}
|
|
3089
|
+
}
|
|
3090
|
+
|
|
3008
3091
|
// src/client/truenas-api-client-v25-10.ts
|
|
3009
3092
|
var TrueNasApiClientV2510 = class extends TrueNasApiClient {
|
|
3010
3093
|
/**
|
|
@@ -3016,48 +3099,34 @@ var TrueNasApiClientV2510 = class extends TrueNasApiClient {
|
|
|
3016
3099
|
*/
|
|
3017
3100
|
createOperations() {
|
|
3018
3101
|
return {
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
options
|
|
3027
|
-
]).pipe(switchMap((jobId) => this.api.trackJob(jobId)))
|
|
3028
|
-
};
|
|
3029
|
-
}
|
|
3030
|
-
/**
|
|
3031
|
-
* Transform VirtualInstanceQuery to unified Container type
|
|
3032
|
-
*/
|
|
3033
|
-
toContainer(instance) {
|
|
3034
|
-
return {
|
|
3035
|
-
id: instance.id,
|
|
3036
|
-
name: instance.name,
|
|
3037
|
-
status: instance.status,
|
|
3038
|
-
autostart: instance.autostart,
|
|
3039
|
-
cpu: instance.cpu,
|
|
3040
|
-
memory: instance.memory,
|
|
3041
|
-
image: instance.image
|
|
3102
|
+
// A polymorphic `.query`, so it goes through the verb rather than
|
|
3103
|
+
// `call`: the directory types the raw method's response as the five-way
|
|
3104
|
+
// union the server may return, and the verb is what fixes it to a list.
|
|
3105
|
+
containerQuery: () => this.api.query("virt.instance.query", [["type", "=", "CONTAINER"]]).pipe(map((instances) => instances.map(toContainer))),
|
|
3106
|
+
containerStart: (id) => this.api.job("virt.instance.start", [id]),
|
|
3107
|
+
containerStop: (id, options) => this.api.job("virt.instance.stop", [id, options]),
|
|
3108
|
+
containerRestart: (id, options) => this.api.job("virt.instance.restart", [id, options])
|
|
3042
3109
|
};
|
|
3043
3110
|
}
|
|
3044
3111
|
};
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
}
|
|
3060
|
-
}
|
|
3112
|
+
function toContainer(instance) {
|
|
3113
|
+
return {
|
|
3114
|
+
id: instance.id,
|
|
3115
|
+
name: instance.name,
|
|
3116
|
+
status: toAppState(instance.status),
|
|
3117
|
+
autostart: instance.autostart,
|
|
3118
|
+
cpu: instance.cpu ?? void 0,
|
|
3119
|
+
memory: instance.memory ?? void 0,
|
|
3120
|
+
// The whole image object, not just its description: `Container.image`
|
|
3121
|
+
// declares only `description`, but the API returns `architecture`, `os`,
|
|
3122
|
+
// `release` and more, and callers were already receiving them. Narrowing
|
|
3123
|
+
// to the declared field would take data away to match a type that was
|
|
3124
|
+
// always an under-declaration — the same call as the v26 `description`
|
|
3125
|
+
// widening, decided the same way.
|
|
3126
|
+
image: instance.image.description === null ? void 0 : { ...instance.image, description: instance.image.description }
|
|
3127
|
+
};
|
|
3128
|
+
}
|
|
3129
|
+
var TrueNasApiClientV26 = class extends TrueNasApiClient {
|
|
3061
3130
|
/**
|
|
3062
3131
|
* Create v26-specific operation mappings
|
|
3063
3132
|
*
|
|
@@ -3067,58 +3136,58 @@ var TrueNasApiClientV26 = class _TrueNasApiClientV26 extends TrueNasApiClient {
|
|
|
3067
3136
|
*/
|
|
3068
3137
|
createOperations() {
|
|
3069
3138
|
return {
|
|
3070
|
-
|
|
3139
|
+
// A polymorphic `.query`, so it goes through the verb rather than
|
|
3140
|
+
// `call`: the directory types the raw method's response as the five-way
|
|
3141
|
+
// union the server may return, and the verb is what fixes it to a list.
|
|
3142
|
+
containerQuery: () => this.api.query("container.query").pipe(
|
|
3143
|
+
map((containers) => containers.map(toContainer2))
|
|
3144
|
+
),
|
|
3071
3145
|
// container.start is synchronous in v26.0.0 - emit null
|
|
3072
|
-
containerStart: (id) => this.api.call("container.start"
|
|
3146
|
+
containerStart: (id) => this.api.call("container.start", [parseInt(id, 10)]).pipe(map(() => null)),
|
|
3073
3147
|
// container.stop emits job updates
|
|
3074
|
-
containerStop: (id, options) => this.api.
|
|
3148
|
+
containerStop: (id, options) => this.api.job("container.stop", [
|
|
3075
3149
|
parseInt(id, 10),
|
|
3076
3150
|
{
|
|
3077
3151
|
force: options.force,
|
|
3078
3152
|
force_after_timeout: options.force
|
|
3079
3153
|
}
|
|
3080
|
-
])
|
|
3154
|
+
]),
|
|
3081
3155
|
// v26.0.0 doesn't have container.restart - chain stop + start
|
|
3082
3156
|
// Emits Job updates during stop, then null when start completes
|
|
3083
3157
|
containerRestart: (id, options) => {
|
|
3084
3158
|
const numericId = parseInt(id, 10);
|
|
3085
|
-
return this.api.
|
|
3159
|
+
return this.api.job("container.stop", [
|
|
3086
3160
|
numericId,
|
|
3087
3161
|
{
|
|
3088
3162
|
force: options.force,
|
|
3089
3163
|
force_after_timeout: options.force
|
|
3090
3164
|
}
|
|
3091
3165
|
]).pipe(
|
|
3092
|
-
// Track stop job until it completes
|
|
3093
|
-
switchMap((stopJobId) => this.api.trackJob(stopJobId)),
|
|
3094
3166
|
// Collect all job updates to ensure stop fully completes
|
|
3095
3167
|
toArray(),
|
|
3096
3168
|
// Re-emit job updates, then call start after stop is done
|
|
3097
3169
|
switchMap(
|
|
3098
3170
|
(jobUpdates) => concat(
|
|
3099
3171
|
from(jobUpdates),
|
|
3100
|
-
this.api.call("container.start"
|
|
3172
|
+
this.api.call("container.start", [numericId]).pipe(map(() => null))
|
|
3101
3173
|
)
|
|
3102
3174
|
)
|
|
3103
3175
|
);
|
|
3104
3176
|
}
|
|
3105
3177
|
};
|
|
3106
3178
|
}
|
|
3107
|
-
/**
|
|
3108
|
-
* Transform v26 ContainerQueryV26 to unified Container type
|
|
3109
|
-
*/
|
|
3110
|
-
toContainer(container) {
|
|
3111
|
-
return {
|
|
3112
|
-
id: container.id.toString(),
|
|
3113
|
-
name: container.name,
|
|
3114
|
-
status: _TrueNasApiClientV26.mapStatus(container.status.state),
|
|
3115
|
-
autostart: container.autostart,
|
|
3116
|
-
description: container.description
|
|
3117
|
-
// cpu and memory are not available in v26 container.query
|
|
3118
|
-
// image is not available in v26 container.query
|
|
3119
|
-
};
|
|
3120
|
-
}
|
|
3121
3179
|
};
|
|
3180
|
+
function toContainer2(container) {
|
|
3181
|
+
const { description } = container;
|
|
3182
|
+
return {
|
|
3183
|
+
id: container.id.toString(),
|
|
3184
|
+
name: container.name,
|
|
3185
|
+
status: toAppState(container.status.state),
|
|
3186
|
+
// Optional in the generated entry, required by `Container`.
|
|
3187
|
+
autostart: container.autostart ?? false,
|
|
3188
|
+
description
|
|
3189
|
+
};
|
|
3190
|
+
}
|
|
3122
3191
|
|
|
3123
3192
|
// src/errors/version-discovery.errors.ts
|
|
3124
3193
|
var VersionDiscoveryError = class extends Error {
|
|
@@ -3508,6 +3577,6 @@ function errorMessageOrDefault(error, fallback) {
|
|
|
3508
3577
|
return fallback;
|
|
3509
3578
|
}
|
|
3510
3579
|
|
|
3511
|
-
export { AuthError, AuthErrorCode, InvalidVersionResponseError, JobState, NoCompatibleVersionsError, SUPPORTED_API_VERSIONS, TrueNasApiClient, TrueNasApiClientV2510, TrueNasApiClientV26, TrueNasAuthMechanism,
|
|
3580
|
+
export { AppState, AuthError, AuthErrorCode, InvalidVersionResponseError, JobState, NoCompatibleVersionsError, SUPPORTED_API_VERSIONS, TrueNasApiClient, TrueNasApiClientV2510, TrueNasApiClientV26, TrueNasAuthMechanism, VersionCompatibility, VersionDiscovery, VersionDiscoveryError, VersionDiscoveryNetworkError, VersionDiscoveryTimeoutError, VersionEndpointNotFoundError, VersionTooNewError, VersionTooOldError, consoleLogger, createTrueNasClient, getApiErrorMessage, isJobFinished, noopLogger, v25_10_0_exports as v25_10_0, v25_10_1_exports as v25_10_1, v25_10_2_exports as v25_10_2, v25_10_3_exports as v25_10_3, v25_10_4_exports as v25_10_4, v25_10_5_exports as v25_10_5, v26_0_0_exports as v26_0_0, v27_0_0_exports as v27_0_0 };
|
|
3512
3581
|
//# sourceMappingURL=index.js.map
|
|
3513
3582
|
//# sourceMappingURL=index.js.map
|