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