@aigne/afs-synology 1.11.0-beta.12
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/LICENSE.md +26 -0
- package/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.cjs +11 -0
- package/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.mjs +10 -0
- package/dist/client.cjs +252 -0
- package/dist/client.d.cts +96 -0
- package/dist/client.d.cts.map +1 -0
- package/dist/client.d.mts +96 -0
- package/dist/client.d.mts.map +1 -0
- package/dist/client.mjs +253 -0
- package/dist/client.mjs.map +1 -0
- package/dist/errors.cjs +37 -0
- package/dist/errors.d.cts +10 -0
- package/dist/errors.d.cts.map +1 -0
- package/dist/errors.d.mts +10 -0
- package/dist/errors.d.mts.map +1 -0
- package/dist/errors.mjs +37 -0
- package/dist/errors.mjs.map +1 -0
- package/dist/formatters.cjs +139 -0
- package/dist/formatters.mjs +128 -0
- package/dist/formatters.mjs.map +1 -0
- package/dist/index.cjs +15 -0
- package/dist/index.d.cts +5 -0
- package/dist/index.d.mts +5 -0
- package/dist/index.mjs +6 -0
- package/dist/synology-afs.cjs +1686 -0
- package/dist/synology-afs.d.cts +232 -0
- package/dist/synology-afs.d.cts.map +1 -0
- package/dist/synology-afs.d.mts +232 -0
- package/dist/synology-afs.d.mts.map +1 -0
- package/dist/synology-afs.mjs +1687 -0
- package/dist/synology-afs.mjs.map +1 -0
- package/dist/types.cjs +44 -0
- package/dist/types.d.cts +174 -0
- package/dist/types.d.cts.map +1 -0
- package/dist/types.d.mts +174 -0
- package/dist/types.d.mts.map +1 -0
- package/dist/types.mjs +42 -0
- package/dist/types.mjs.map +1 -0
- package/package.json +57 -0
|
@@ -0,0 +1,1687 @@
|
|
|
1
|
+
import { AFSSynologyConfigSchema, KINDS } from "./types.mjs";
|
|
2
|
+
import { SynologyClient } from "./client.mjs";
|
|
3
|
+
import { formatBackupTaskInfo, formatBytes, formatContainerInfo, formatDiskInfo, formatHardwareInfo, formatImageInfo, formatPackageInfo, formatPoolInfo, formatShareInfo, formatSystemStatus, formatSystemUtilization, formatVolumeInfo } from "./formatters.mjs";
|
|
4
|
+
import { __decorate } from "./_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.mjs";
|
|
5
|
+
import { AFSBaseProvider, AFSError, AFSNotFoundError, Actions, Explain, List, Meta, Read, Search, Stat } from "@aigne/afs";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
import { joinURL } from "ufo";
|
|
8
|
+
|
|
9
|
+
//#region src/synology-afs.ts
|
|
10
|
+
/**
|
|
11
|
+
* AFSSynology - Synology NAS AFS Provider
|
|
12
|
+
*
|
|
13
|
+
* Maps Synology DSM management capabilities to AFS filesystem interface.
|
|
14
|
+
* File read/write goes through afs-fs (NFS/SMB); this provider focuses on:
|
|
15
|
+
* - System health monitoring
|
|
16
|
+
* - Storage status (disks, pools, volumes)
|
|
17
|
+
* - Docker container management (including start/stop/restart)
|
|
18
|
+
* - Backup monitoring and task triggering
|
|
19
|
+
* - Package management (start/stop)
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* Synology NAS AFS Provider
|
|
23
|
+
*
|
|
24
|
+
* Readwrite mode: supports exec operations (container start/stop/restart,
|
|
25
|
+
* backup trigger, package start/stop). Write and delete are never supported.
|
|
26
|
+
*/
|
|
27
|
+
var AFSSynology = class AFSSynology extends AFSBaseProvider {
|
|
28
|
+
static securityProfiles() {
|
|
29
|
+
return {
|
|
30
|
+
admin: {
|
|
31
|
+
actionPolicy: "full",
|
|
32
|
+
sensitivity: "full"
|
|
33
|
+
},
|
|
34
|
+
user: {
|
|
35
|
+
actionPolicy: "standard",
|
|
36
|
+
sensitiveFields: ["serial_number"],
|
|
37
|
+
sensitivity: "redacted"
|
|
38
|
+
},
|
|
39
|
+
kid: {
|
|
40
|
+
actionPolicy: "safe",
|
|
41
|
+
accessMode: "readonly",
|
|
42
|
+
blockedActions: ["stop", "restart"],
|
|
43
|
+
sensitiveFields: ["serial_number"],
|
|
44
|
+
sensitivity: "redacted"
|
|
45
|
+
},
|
|
46
|
+
guest: {
|
|
47
|
+
actionPolicy: "safe",
|
|
48
|
+
accessMode: "readonly",
|
|
49
|
+
sensitiveFields: ["serial_number"],
|
|
50
|
+
sensitivity: "redacted"
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
name;
|
|
55
|
+
description;
|
|
56
|
+
accessMode = "readwrite";
|
|
57
|
+
client;
|
|
58
|
+
systemInfo = null;
|
|
59
|
+
utilization = null;
|
|
60
|
+
disks = null;
|
|
61
|
+
pools = null;
|
|
62
|
+
volumes = null;
|
|
63
|
+
containers = null;
|
|
64
|
+
images = null;
|
|
65
|
+
shares = null;
|
|
66
|
+
packages = null;
|
|
67
|
+
backupTasks = null;
|
|
68
|
+
constructor(config) {
|
|
69
|
+
super();
|
|
70
|
+
const { uri: _uri, ...cleanConfig } = config;
|
|
71
|
+
const validated = AFSSynologyConfigSchema.parse(cleanConfig);
|
|
72
|
+
if (!validated.url.startsWith("http://") && !validated.url.startsWith("https://")) throw new AFSError("URL must start with http:// or https://", "AFS_INVALID_PARAMETER");
|
|
73
|
+
this.name = validated.name || "synology";
|
|
74
|
+
this.description = validated.description || `Synology NAS at ${validated.url}`;
|
|
75
|
+
this.client = new SynologyClient({
|
|
76
|
+
url: validated.url,
|
|
77
|
+
account: validated.account,
|
|
78
|
+
password: validated.password,
|
|
79
|
+
otpCode: validated.otpCode,
|
|
80
|
+
deviceId: validated.deviceId,
|
|
81
|
+
verifySsl: validated.verifySsl
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
static schema() {
|
|
85
|
+
return AFSSynologyConfigSchema;
|
|
86
|
+
}
|
|
87
|
+
static manifest() {
|
|
88
|
+
return {
|
|
89
|
+
name: "synology",
|
|
90
|
+
description: "Synology NAS management — system health, storage status, Docker containers, backup monitoring.\n- Monitor CPU, RAM, disk health, temperature\n- View Docker containers, images, networks\n- Start/stop/restart Docker containers\n- Check Hyper Backup task status and trigger backups\n- Start/stop DSM packages\n- Path structure: `/system/`, `/storage/`, `/docker/`, `/shares/`, `/packages/`, `/backup/`",
|
|
91
|
+
uriTemplate: "synology://{host}",
|
|
92
|
+
category: "storage",
|
|
93
|
+
schema: z.object({ host: z.string().optional() }),
|
|
94
|
+
tags: [
|
|
95
|
+
"synology",
|
|
96
|
+
"nas",
|
|
97
|
+
"docker",
|
|
98
|
+
"storage",
|
|
99
|
+
"backup"
|
|
100
|
+
],
|
|
101
|
+
capabilityTags: [
|
|
102
|
+
"read-write",
|
|
103
|
+
"crud",
|
|
104
|
+
"search",
|
|
105
|
+
"auth:token",
|
|
106
|
+
"remote",
|
|
107
|
+
"http",
|
|
108
|
+
"on-premise"
|
|
109
|
+
],
|
|
110
|
+
security: {
|
|
111
|
+
riskLevel: "external",
|
|
112
|
+
resourceAccess: ["internet", "local-network"],
|
|
113
|
+
requires: ["docker"],
|
|
114
|
+
dataSensitivity: ["system-config", "media"],
|
|
115
|
+
notes: ["Manages NAS system — can start/stop Docker containers and DSM packages"]
|
|
116
|
+
},
|
|
117
|
+
capabilities: {
|
|
118
|
+
network: { egress: true },
|
|
119
|
+
secrets: ["synology/password"]
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
static treeSchema() {
|
|
124
|
+
return {
|
|
125
|
+
operations: [
|
|
126
|
+
"list",
|
|
127
|
+
"read",
|
|
128
|
+
"search",
|
|
129
|
+
"exec",
|
|
130
|
+
"stat",
|
|
131
|
+
"explain"
|
|
132
|
+
],
|
|
133
|
+
tree: {
|
|
134
|
+
"/": { kind: "nas:root" },
|
|
135
|
+
"/system": {
|
|
136
|
+
kind: "nas:system-group",
|
|
137
|
+
operations: ["list"]
|
|
138
|
+
},
|
|
139
|
+
"/system/status": {
|
|
140
|
+
kind: "nas:system-info",
|
|
141
|
+
operations: ["read"]
|
|
142
|
+
},
|
|
143
|
+
"/system/utilization": {
|
|
144
|
+
kind: "nas:system-info",
|
|
145
|
+
operations: ["read"]
|
|
146
|
+
},
|
|
147
|
+
"/system/hardware": {
|
|
148
|
+
kind: "nas:system-info",
|
|
149
|
+
operations: ["read"]
|
|
150
|
+
},
|
|
151
|
+
"/storage": {
|
|
152
|
+
kind: "nas:storage-group",
|
|
153
|
+
operations: ["list"]
|
|
154
|
+
},
|
|
155
|
+
"/storage/disks": {
|
|
156
|
+
kind: "afs:directory",
|
|
157
|
+
operations: ["list"]
|
|
158
|
+
},
|
|
159
|
+
"/storage/disks/{diskId}": {
|
|
160
|
+
kind: "nas:disk",
|
|
161
|
+
operations: ["read"]
|
|
162
|
+
},
|
|
163
|
+
"/storage/pools": {
|
|
164
|
+
kind: "afs:directory",
|
|
165
|
+
operations: ["list"]
|
|
166
|
+
},
|
|
167
|
+
"/storage/pools/{poolId}": {
|
|
168
|
+
kind: "nas:storage-pool",
|
|
169
|
+
operations: ["read"]
|
|
170
|
+
},
|
|
171
|
+
"/storage/volumes": {
|
|
172
|
+
kind: "afs:directory",
|
|
173
|
+
operations: ["list"]
|
|
174
|
+
},
|
|
175
|
+
"/storage/volumes/{volumeId}": {
|
|
176
|
+
kind: "nas:volume",
|
|
177
|
+
operations: ["read"]
|
|
178
|
+
},
|
|
179
|
+
"/docker": {
|
|
180
|
+
kind: "nas:docker-group",
|
|
181
|
+
operations: ["list"]
|
|
182
|
+
},
|
|
183
|
+
"/docker/containers": {
|
|
184
|
+
kind: "afs:directory",
|
|
185
|
+
operations: ["list"]
|
|
186
|
+
},
|
|
187
|
+
"/docker/containers/{name}": {
|
|
188
|
+
kind: "nas:container",
|
|
189
|
+
operations: ["read", "exec"],
|
|
190
|
+
actions: [
|
|
191
|
+
"start",
|
|
192
|
+
"stop",
|
|
193
|
+
"restart"
|
|
194
|
+
],
|
|
195
|
+
destructive: ["stop"]
|
|
196
|
+
},
|
|
197
|
+
"/docker/images": {
|
|
198
|
+
kind: "afs:directory",
|
|
199
|
+
operations: ["list"]
|
|
200
|
+
},
|
|
201
|
+
"/docker/images/{name}": {
|
|
202
|
+
kind: "nas:docker-image",
|
|
203
|
+
operations: ["read"]
|
|
204
|
+
},
|
|
205
|
+
"/docker/networks": {
|
|
206
|
+
kind: "afs:directory",
|
|
207
|
+
operations: ["list"]
|
|
208
|
+
},
|
|
209
|
+
"/docker/projects": {
|
|
210
|
+
kind: "afs:directory",
|
|
211
|
+
operations: ["list"]
|
|
212
|
+
},
|
|
213
|
+
"/shares": {
|
|
214
|
+
kind: "afs:directory",
|
|
215
|
+
operations: ["list"]
|
|
216
|
+
},
|
|
217
|
+
"/shares/{name}": {
|
|
218
|
+
kind: "nas:shared-folder",
|
|
219
|
+
operations: ["read"]
|
|
220
|
+
},
|
|
221
|
+
"/packages": {
|
|
222
|
+
kind: "afs:directory",
|
|
223
|
+
operations: ["list"]
|
|
224
|
+
},
|
|
225
|
+
"/packages/{name}": {
|
|
226
|
+
kind: "nas:package",
|
|
227
|
+
operations: ["read", "exec"],
|
|
228
|
+
actions: ["start", "stop"]
|
|
229
|
+
},
|
|
230
|
+
"/backup": {
|
|
231
|
+
kind: "afs:directory",
|
|
232
|
+
operations: ["list"]
|
|
233
|
+
},
|
|
234
|
+
"/backup/tasks": {
|
|
235
|
+
kind: "afs:directory",
|
|
236
|
+
operations: ["list"]
|
|
237
|
+
},
|
|
238
|
+
"/backup/tasks/{taskId}": {
|
|
239
|
+
kind: "nas:backup-task",
|
|
240
|
+
operations: ["read", "exec"],
|
|
241
|
+
actions: ["run"]
|
|
242
|
+
}
|
|
243
|
+
},
|
|
244
|
+
auth: {
|
|
245
|
+
type: "custom",
|
|
246
|
+
env: [
|
|
247
|
+
"SYNOLOGY_URL",
|
|
248
|
+
"SYNOLOGY_ACCOUNT",
|
|
249
|
+
"SYNOLOGY_PASSWORD"
|
|
250
|
+
]
|
|
251
|
+
},
|
|
252
|
+
bestFor: [
|
|
253
|
+
"NAS monitoring",
|
|
254
|
+
"Docker management",
|
|
255
|
+
"backup monitoring"
|
|
256
|
+
],
|
|
257
|
+
notFor: ["file read/write", "database queries"]
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
static async load({ config } = {}) {
|
|
261
|
+
return new AFSSynology(AFSSynologyConfigSchema.parse(config ?? {}));
|
|
262
|
+
}
|
|
263
|
+
async getSystemInfo() {
|
|
264
|
+
if (!this.systemInfo) this.systemInfo = await this.client.request({
|
|
265
|
+
api: "SYNO.Core.System",
|
|
266
|
+
method: "info",
|
|
267
|
+
version: 1
|
|
268
|
+
});
|
|
269
|
+
return this.systemInfo;
|
|
270
|
+
}
|
|
271
|
+
async getUtilization() {
|
|
272
|
+
if (!this.utilization) this.utilization = await this.client.request({
|
|
273
|
+
api: "SYNO.Core.System.Utilization",
|
|
274
|
+
method: "get",
|
|
275
|
+
version: 1
|
|
276
|
+
});
|
|
277
|
+
return this.utilization;
|
|
278
|
+
}
|
|
279
|
+
async getDisks() {
|
|
280
|
+
if (!this.disks) this.disks = (await this.client.request({
|
|
281
|
+
api: "SYNO.Storage.CGI.Storage",
|
|
282
|
+
method: "load_info",
|
|
283
|
+
version: 1
|
|
284
|
+
})).disks || [];
|
|
285
|
+
return this.disks;
|
|
286
|
+
}
|
|
287
|
+
async getPools() {
|
|
288
|
+
if (!this.pools) this.pools = (await this.client.request({
|
|
289
|
+
api: "SYNO.Storage.CGI.Storage",
|
|
290
|
+
method: "load_info",
|
|
291
|
+
version: 1
|
|
292
|
+
})).storagePools || [];
|
|
293
|
+
return this.pools;
|
|
294
|
+
}
|
|
295
|
+
async getVolumes() {
|
|
296
|
+
if (!this.volumes) this.volumes = (await this.client.request({
|
|
297
|
+
api: "SYNO.Storage.CGI.Storage",
|
|
298
|
+
method: "load_info",
|
|
299
|
+
version: 1
|
|
300
|
+
})).volumes || [];
|
|
301
|
+
return this.volumes;
|
|
302
|
+
}
|
|
303
|
+
async getContainers() {
|
|
304
|
+
if (!this.containers) this.containers = (await this.client.request({
|
|
305
|
+
api: "SYNO.Docker.Container",
|
|
306
|
+
method: "list",
|
|
307
|
+
version: 1,
|
|
308
|
+
extra: {
|
|
309
|
+
limit: -1,
|
|
310
|
+
offset: 0,
|
|
311
|
+
type: "all"
|
|
312
|
+
}
|
|
313
|
+
})).containers || [];
|
|
314
|
+
return this.containers;
|
|
315
|
+
}
|
|
316
|
+
async getImages() {
|
|
317
|
+
if (!this.images) this.images = (await this.client.request({
|
|
318
|
+
api: "SYNO.Docker.Image",
|
|
319
|
+
method: "list",
|
|
320
|
+
version: 1,
|
|
321
|
+
extra: {
|
|
322
|
+
limit: -1,
|
|
323
|
+
offset: 0
|
|
324
|
+
}
|
|
325
|
+
})).images || [];
|
|
326
|
+
return this.images;
|
|
327
|
+
}
|
|
328
|
+
async getShares() {
|
|
329
|
+
if (!this.shares) this.shares = (await this.client.request({
|
|
330
|
+
api: "SYNO.Core.Share",
|
|
331
|
+
method: "list",
|
|
332
|
+
version: 1
|
|
333
|
+
})).shares || [];
|
|
334
|
+
return this.shares;
|
|
335
|
+
}
|
|
336
|
+
async getPackages() {
|
|
337
|
+
if (!this.packages) this.packages = (await this.client.request({
|
|
338
|
+
api: "SYNO.Core.Package",
|
|
339
|
+
method: "list",
|
|
340
|
+
version: 1
|
|
341
|
+
})).packages || [];
|
|
342
|
+
return this.packages;
|
|
343
|
+
}
|
|
344
|
+
async getBackupTasks() {
|
|
345
|
+
if (!this.backupTasks) this.backupTasks = (await this.client.request({
|
|
346
|
+
api: "SYNO.Backup.Task",
|
|
347
|
+
method: "list",
|
|
348
|
+
version: 1
|
|
349
|
+
})).tasks || [];
|
|
350
|
+
return this.backupTasks;
|
|
351
|
+
}
|
|
352
|
+
/** Sanitize image name for use as path segment (replace / with -) */
|
|
353
|
+
sanitizeImageName(repository, _tag) {
|
|
354
|
+
return `${repository.replace(/\//g, "-")}`;
|
|
355
|
+
}
|
|
356
|
+
/** Invalidate container cache after exec operations */
|
|
357
|
+
invalidateContainerCache() {
|
|
358
|
+
this.containers = null;
|
|
359
|
+
this.client.invalidateCache();
|
|
360
|
+
}
|
|
361
|
+
/** Invalidate package cache after exec operations */
|
|
362
|
+
invalidatePackageCache() {
|
|
363
|
+
this.packages = null;
|
|
364
|
+
}
|
|
365
|
+
/** Invalidate backup task cache after exec operations */
|
|
366
|
+
invalidateBackupTaskCache() {
|
|
367
|
+
this.backupTasks = null;
|
|
368
|
+
}
|
|
369
|
+
async readCapabilities(_ctx) {
|
|
370
|
+
return {
|
|
371
|
+
id: "/.meta/.capabilities",
|
|
372
|
+
path: "/.meta/.capabilities",
|
|
373
|
+
content: {
|
|
374
|
+
schemaVersion: 1,
|
|
375
|
+
provider: this.name,
|
|
376
|
+
description: this.description || "Synology NAS management provider",
|
|
377
|
+
tools: [],
|
|
378
|
+
actions: [],
|
|
379
|
+
operations: this.getOperationsDeclaration()
|
|
380
|
+
},
|
|
381
|
+
meta: { kind: "afs:capabilities" }
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
async metaRoot(_ctx) {
|
|
385
|
+
const info = await this.getSystemInfo();
|
|
386
|
+
return {
|
|
387
|
+
id: "",
|
|
388
|
+
path: "/.meta",
|
|
389
|
+
meta: {
|
|
390
|
+
kind: KINDS.NAS,
|
|
391
|
+
model: info.model,
|
|
392
|
+
version: info.version,
|
|
393
|
+
serial: info.serial,
|
|
394
|
+
childrenCount: 6,
|
|
395
|
+
description: `Synology ${info.model} running DSM ${info.version}`
|
|
396
|
+
}
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
async metaSystem(_ctx) {
|
|
400
|
+
return {
|
|
401
|
+
id: "system",
|
|
402
|
+
path: "/system/.meta",
|
|
403
|
+
meta: {
|
|
404
|
+
kind: KINDS.SYSTEM_GROUP,
|
|
405
|
+
childrenCount: 3,
|
|
406
|
+
description: "System health and resource monitoring"
|
|
407
|
+
}
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
async metaSystemItem(ctx) {
|
|
411
|
+
const { item } = ctx.params;
|
|
412
|
+
const descriptions = {
|
|
413
|
+
status: "System health status summary",
|
|
414
|
+
utilization: "CPU, RAM, disk I/O, network statistics",
|
|
415
|
+
hardware: "Temperature, fan speed, power status"
|
|
416
|
+
};
|
|
417
|
+
if (!descriptions[item]) throw new AFSNotFoundError(joinURL("/system", item));
|
|
418
|
+
return {
|
|
419
|
+
id: item,
|
|
420
|
+
path: joinURL("/system", item, ".meta"),
|
|
421
|
+
meta: {
|
|
422
|
+
kind: KINDS.SYSTEM_INFO,
|
|
423
|
+
childrenCount: 0,
|
|
424
|
+
description: descriptions[item]
|
|
425
|
+
}
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
async metaStorage(_ctx) {
|
|
429
|
+
return {
|
|
430
|
+
id: "storage",
|
|
431
|
+
path: "/storage/.meta",
|
|
432
|
+
meta: {
|
|
433
|
+
kind: KINDS.STORAGE_GROUP,
|
|
434
|
+
childrenCount: 3,
|
|
435
|
+
description: "Physical disks, storage pools, and volumes"
|
|
436
|
+
}
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
async metaDisks(_ctx) {
|
|
440
|
+
return {
|
|
441
|
+
id: "disks",
|
|
442
|
+
path: "/storage/disks/.meta",
|
|
443
|
+
meta: {
|
|
444
|
+
childrenCount: (await this.getDisks()).length,
|
|
445
|
+
description: "Physical disk drives"
|
|
446
|
+
}
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
async metaDisk(ctx) {
|
|
450
|
+
const disk = (await this.getDisks()).find((d) => d.id === ctx.params.diskId);
|
|
451
|
+
if (!disk) throw new AFSNotFoundError(joinURL("/storage/disks", ctx.params.diskId));
|
|
452
|
+
return {
|
|
453
|
+
id: disk.id,
|
|
454
|
+
path: joinURL("/storage/disks", disk.id, ".meta"),
|
|
455
|
+
meta: {
|
|
456
|
+
kind: KINDS.DISK,
|
|
457
|
+
id: disk.id,
|
|
458
|
+
childrenCount: 0,
|
|
459
|
+
description: `${disk.model} - ${formatBytes(disk.size_total)} - ${disk.smart_status || disk.status}`
|
|
460
|
+
}
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
async metaPools(_ctx) {
|
|
464
|
+
return {
|
|
465
|
+
id: "pools",
|
|
466
|
+
path: "/storage/pools/.meta",
|
|
467
|
+
meta: {
|
|
468
|
+
childrenCount: (await this.getPools()).length,
|
|
469
|
+
description: "RAID storage pools"
|
|
470
|
+
}
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
async metaPool(ctx) {
|
|
474
|
+
const pool = (await this.getPools()).find((p) => p.id === ctx.params.poolId);
|
|
475
|
+
if (!pool) throw new AFSNotFoundError(joinURL("/storage/pools", ctx.params.poolId));
|
|
476
|
+
return {
|
|
477
|
+
id: pool.id,
|
|
478
|
+
path: joinURL("/storage/pools", pool.id, ".meta"),
|
|
479
|
+
meta: {
|
|
480
|
+
kind: KINDS.STORAGE_POOL,
|
|
481
|
+
id: pool.id,
|
|
482
|
+
childrenCount: 0,
|
|
483
|
+
description: `${pool.raidType || "unknown"} pool - ${pool.status}`
|
|
484
|
+
}
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
async metaVolumes(_ctx) {
|
|
488
|
+
return {
|
|
489
|
+
id: "volumes",
|
|
490
|
+
path: "/storage/volumes/.meta",
|
|
491
|
+
meta: {
|
|
492
|
+
childrenCount: (await this.getVolumes()).length,
|
|
493
|
+
description: "Logical volumes"
|
|
494
|
+
}
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
async metaVolume(ctx) {
|
|
498
|
+
const volume = (await this.getVolumes()).find((v) => v.id === ctx.params.volumeId);
|
|
499
|
+
if (!volume) throw new AFSNotFoundError(joinURL("/storage/volumes", ctx.params.volumeId));
|
|
500
|
+
const usedPercent = Math.round(volume.size.used / volume.size.total * 100);
|
|
501
|
+
return {
|
|
502
|
+
id: volume.id,
|
|
503
|
+
path: joinURL("/storage/volumes", volume.id, ".meta"),
|
|
504
|
+
meta: {
|
|
505
|
+
kind: KINDS.VOLUME,
|
|
506
|
+
id: volume.id,
|
|
507
|
+
childrenCount: 0,
|
|
508
|
+
description: `${volume.fs_type} - ${usedPercent}% used`
|
|
509
|
+
}
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
async metaDocker(_ctx) {
|
|
513
|
+
return {
|
|
514
|
+
id: "docker",
|
|
515
|
+
path: "/docker/.meta",
|
|
516
|
+
meta: {
|
|
517
|
+
kind: KINDS.DOCKER_GROUP,
|
|
518
|
+
childrenCount: 4,
|
|
519
|
+
description: "Docker containers, images, networks, and projects"
|
|
520
|
+
}
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
async metaContainers(_ctx) {
|
|
524
|
+
return {
|
|
525
|
+
id: "containers",
|
|
526
|
+
path: "/docker/containers/.meta",
|
|
527
|
+
meta: {
|
|
528
|
+
childrenCount: -1,
|
|
529
|
+
description: "Running and stopped containers"
|
|
530
|
+
}
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
async metaContainer(ctx) {
|
|
534
|
+
const container = (await this.getContainers()).find((c) => c.name === ctx.params.containerName);
|
|
535
|
+
if (!container) throw new AFSNotFoundError(joinURL("/docker/containers", ctx.params.containerName));
|
|
536
|
+
return {
|
|
537
|
+
id: container.name,
|
|
538
|
+
path: joinURL("/docker/containers", container.name, ".meta"),
|
|
539
|
+
meta: {
|
|
540
|
+
kind: KINDS.CONTAINER,
|
|
541
|
+
id: container.name,
|
|
542
|
+
childrenCount: 0,
|
|
543
|
+
description: `${container.image} - ${container.status}`
|
|
544
|
+
}
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
async metaImages(_ctx) {
|
|
548
|
+
return {
|
|
549
|
+
id: "images",
|
|
550
|
+
path: "/docker/images/.meta",
|
|
551
|
+
meta: {
|
|
552
|
+
childrenCount: -1,
|
|
553
|
+
description: "Local Docker images"
|
|
554
|
+
}
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
async metaImage(ctx) {
|
|
558
|
+
const image = (await this.getImages()).find((i) => this.sanitizeImageName(i.repository, i.tag) === ctx.params.imageName);
|
|
559
|
+
if (!image) throw new AFSNotFoundError(joinURL("/docker/images", ctx.params.imageName));
|
|
560
|
+
return {
|
|
561
|
+
id: `${image.repository}:${image.tag}`,
|
|
562
|
+
path: joinURL("/docker/images", ctx.params.imageName, ".meta"),
|
|
563
|
+
meta: {
|
|
564
|
+
kind: KINDS.DOCKER_IMAGE,
|
|
565
|
+
id: `${image.repository}:${image.tag}`,
|
|
566
|
+
childrenCount: 0
|
|
567
|
+
}
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
async metaNetworks(_ctx) {
|
|
571
|
+
return {
|
|
572
|
+
id: "networks",
|
|
573
|
+
path: "/docker/networks/.meta",
|
|
574
|
+
meta: {
|
|
575
|
+
childrenCount: 0,
|
|
576
|
+
description: "Docker networks"
|
|
577
|
+
}
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
async metaProjects(_ctx) {
|
|
581
|
+
return {
|
|
582
|
+
id: "projects",
|
|
583
|
+
path: "/docker/projects/.meta",
|
|
584
|
+
meta: {
|
|
585
|
+
childrenCount: 0,
|
|
586
|
+
description: "Docker Compose projects (DSM 7.2+)"
|
|
587
|
+
}
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
async metaShares(_ctx) {
|
|
591
|
+
return {
|
|
592
|
+
id: "shares",
|
|
593
|
+
path: "/shares/.meta",
|
|
594
|
+
meta: {
|
|
595
|
+
childrenCount: (await this.getShares()).length,
|
|
596
|
+
description: "Shared folders"
|
|
597
|
+
}
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
async metaShare(ctx) {
|
|
601
|
+
const share = (await this.getShares()).find((s) => s.name === ctx.params.shareName);
|
|
602
|
+
if (!share) throw new AFSNotFoundError(joinURL("/shares", ctx.params.shareName));
|
|
603
|
+
return {
|
|
604
|
+
id: share.name,
|
|
605
|
+
path: joinURL("/shares", share.name, ".meta"),
|
|
606
|
+
meta: {
|
|
607
|
+
kind: KINDS.SHARED_FOLDER,
|
|
608
|
+
id: share.name,
|
|
609
|
+
childrenCount: 0,
|
|
610
|
+
description: share.path
|
|
611
|
+
}
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
async metaPackages(_ctx) {
|
|
615
|
+
return {
|
|
616
|
+
id: "packages",
|
|
617
|
+
path: "/packages/.meta",
|
|
618
|
+
meta: {
|
|
619
|
+
childrenCount: (await this.getPackages()).length,
|
|
620
|
+
description: "Installed DSM packages"
|
|
621
|
+
}
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
async metaPackage(ctx) {
|
|
625
|
+
const pkg = (await this.getPackages()).find((p) => p.id === ctx.params.packageName);
|
|
626
|
+
if (!pkg) throw new AFSNotFoundError(joinURL("/packages", ctx.params.packageName));
|
|
627
|
+
const updateStr = pkg.has_update ? " (update available)" : "";
|
|
628
|
+
return {
|
|
629
|
+
id: pkg.id,
|
|
630
|
+
path: joinURL("/packages", pkg.id, ".meta"),
|
|
631
|
+
meta: {
|
|
632
|
+
kind: KINDS.PACKAGE,
|
|
633
|
+
id: pkg.id,
|
|
634
|
+
childrenCount: 0,
|
|
635
|
+
description: `${pkg.name} v${pkg.version} - ${pkg.status}${updateStr}`
|
|
636
|
+
}
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
async metaBackup(_ctx) {
|
|
640
|
+
return {
|
|
641
|
+
id: "backup",
|
|
642
|
+
path: "/backup/.meta",
|
|
643
|
+
meta: {
|
|
644
|
+
childrenCount: 1,
|
|
645
|
+
description: "Backup management"
|
|
646
|
+
}
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
async metaBackupTasks(_ctx) {
|
|
650
|
+
return {
|
|
651
|
+
id: "tasks",
|
|
652
|
+
path: "/backup/tasks/.meta",
|
|
653
|
+
meta: {
|
|
654
|
+
childrenCount: (await this.getBackupTasks()).length,
|
|
655
|
+
description: "Hyper Backup tasks"
|
|
656
|
+
}
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
async metaBackupTask(ctx) {
|
|
660
|
+
const task = (await this.getBackupTasks()).find((t) => t.name === ctx.params.taskName);
|
|
661
|
+
if (!task) throw new AFSNotFoundError(joinURL("/backup/tasks", ctx.params.taskName));
|
|
662
|
+
const lastRunDate = task.last_run ? task.last_run.split("T")[0] : "never";
|
|
663
|
+
return {
|
|
664
|
+
id: task.name,
|
|
665
|
+
path: joinURL("/backup/tasks", task.name, ".meta"),
|
|
666
|
+
meta: {
|
|
667
|
+
kind: KINDS.BACKUP_TASK,
|
|
668
|
+
id: task.name,
|
|
669
|
+
childrenCount: 0,
|
|
670
|
+
description: `${task.status} - last run ${lastRunDate}`
|
|
671
|
+
}
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
async listRoot(_ctx) {
|
|
675
|
+
return { data: [
|
|
676
|
+
this.buildEntry("/system", { meta: {
|
|
677
|
+
kind: KINDS.SYSTEM_GROUP,
|
|
678
|
+
childrenCount: 3,
|
|
679
|
+
description: "System health and resource monitoring"
|
|
680
|
+
} }),
|
|
681
|
+
this.buildEntry("/storage", { meta: {
|
|
682
|
+
kind: KINDS.STORAGE_GROUP,
|
|
683
|
+
childrenCount: 3,
|
|
684
|
+
description: "Physical disks, storage pools, and volumes"
|
|
685
|
+
} }),
|
|
686
|
+
this.buildEntry("/docker", { meta: {
|
|
687
|
+
kind: KINDS.DOCKER_GROUP,
|
|
688
|
+
childrenCount: 4,
|
|
689
|
+
description: "Docker containers, images, networks, and projects"
|
|
690
|
+
} }),
|
|
691
|
+
this.buildEntry("/shares", { meta: {
|
|
692
|
+
kind: "nas:shared-folder-group",
|
|
693
|
+
childrenCount: -1,
|
|
694
|
+
description: "Shared folders"
|
|
695
|
+
} }),
|
|
696
|
+
this.buildEntry("/packages", { meta: {
|
|
697
|
+
kind: "nas:package-group",
|
|
698
|
+
childrenCount: -1,
|
|
699
|
+
description: "Installed DSM packages"
|
|
700
|
+
} }),
|
|
701
|
+
this.buildEntry("/backup", { meta: {
|
|
702
|
+
kind: "nas:backup-group",
|
|
703
|
+
childrenCount: 1,
|
|
704
|
+
description: "Backup management"
|
|
705
|
+
} })
|
|
706
|
+
] };
|
|
707
|
+
}
|
|
708
|
+
async listSystem(_ctx) {
|
|
709
|
+
return { data: [
|
|
710
|
+
this.buildEntry("/system/status", { meta: {
|
|
711
|
+
kind: KINDS.SYSTEM_INFO,
|
|
712
|
+
childrenCount: 0,
|
|
713
|
+
description: "System health status summary"
|
|
714
|
+
} }),
|
|
715
|
+
this.buildEntry("/system/utilization", { meta: {
|
|
716
|
+
kind: KINDS.SYSTEM_INFO,
|
|
717
|
+
childrenCount: 0,
|
|
718
|
+
description: "CPU, RAM, disk I/O, network statistics"
|
|
719
|
+
} }),
|
|
720
|
+
this.buildEntry("/system/hardware", { meta: {
|
|
721
|
+
kind: KINDS.SYSTEM_INFO,
|
|
722
|
+
childrenCount: 0,
|
|
723
|
+
description: "Temperature, fan speed, power status"
|
|
724
|
+
} })
|
|
725
|
+
] };
|
|
726
|
+
}
|
|
727
|
+
async listStorage(_ctx) {
|
|
728
|
+
const [disks, pools, volumes] = await Promise.all([
|
|
729
|
+
this.getDisks(),
|
|
730
|
+
this.getPools(),
|
|
731
|
+
this.getVolumes()
|
|
732
|
+
]);
|
|
733
|
+
return { data: [
|
|
734
|
+
this.buildEntry("/storage/disks", { meta: {
|
|
735
|
+
childrenCount: disks.length,
|
|
736
|
+
description: "Physical disk drives"
|
|
737
|
+
} }),
|
|
738
|
+
this.buildEntry("/storage/pools", { meta: {
|
|
739
|
+
childrenCount: pools.length,
|
|
740
|
+
description: "RAID storage pools"
|
|
741
|
+
} }),
|
|
742
|
+
this.buildEntry("/storage/volumes", { meta: {
|
|
743
|
+
childrenCount: volumes.length,
|
|
744
|
+
description: "Logical volumes"
|
|
745
|
+
} })
|
|
746
|
+
] };
|
|
747
|
+
}
|
|
748
|
+
async listDisks(_ctx) {
|
|
749
|
+
return { data: (await this.getDisks()).map((d) => this.buildEntry(joinURL("/storage/disks", d.id), { meta: {
|
|
750
|
+
kind: KINDS.DISK,
|
|
751
|
+
id: d.id,
|
|
752
|
+
childrenCount: 0,
|
|
753
|
+
description: `${d.model} - ${formatBytes(d.size_total)} - ${d.smart_status || d.status}`
|
|
754
|
+
} })) };
|
|
755
|
+
}
|
|
756
|
+
async listPools(_ctx) {
|
|
757
|
+
return { data: (await this.getPools()).map((p) => this.buildEntry(joinURL("/storage/pools", p.id), { meta: {
|
|
758
|
+
kind: KINDS.STORAGE_POOL,
|
|
759
|
+
id: p.id,
|
|
760
|
+
childrenCount: 0,
|
|
761
|
+
description: `${p.raidType || "unknown"} pool - ${p.status}`
|
|
762
|
+
} })) };
|
|
763
|
+
}
|
|
764
|
+
async listVolumes(_ctx) {
|
|
765
|
+
return { data: (await this.getVolumes()).map((v) => {
|
|
766
|
+
const usedPercent = Math.round(v.size.used / v.size.total * 100);
|
|
767
|
+
return this.buildEntry(joinURL("/storage/volumes", v.id), { meta: {
|
|
768
|
+
kind: KINDS.VOLUME,
|
|
769
|
+
id: v.id,
|
|
770
|
+
childrenCount: 0,
|
|
771
|
+
description: `${v.fs_type} - ${usedPercent}% used`
|
|
772
|
+
} });
|
|
773
|
+
}) };
|
|
774
|
+
}
|
|
775
|
+
async listDocker(_ctx) {
|
|
776
|
+
return { data: [
|
|
777
|
+
this.buildEntry("/docker/containers", { meta: {
|
|
778
|
+
childrenCount: -1,
|
|
779
|
+
description: "Running and stopped containers"
|
|
780
|
+
} }),
|
|
781
|
+
this.buildEntry("/docker/images", { meta: {
|
|
782
|
+
childrenCount: -1,
|
|
783
|
+
description: "Local Docker images"
|
|
784
|
+
} }),
|
|
785
|
+
this.buildEntry("/docker/networks", { meta: {
|
|
786
|
+
childrenCount: 0,
|
|
787
|
+
description: "Docker networks"
|
|
788
|
+
} }),
|
|
789
|
+
this.buildEntry("/docker/projects", { meta: {
|
|
790
|
+
childrenCount: 0,
|
|
791
|
+
description: "Docker Compose projects (DSM 7.2+)"
|
|
792
|
+
} })
|
|
793
|
+
] };
|
|
794
|
+
}
|
|
795
|
+
async listContainers(_ctx) {
|
|
796
|
+
return { data: (await this.getContainers()).map((c) => this.buildEntry(joinURL("/docker/containers", c.name), { meta: {
|
|
797
|
+
kind: KINDS.CONTAINER,
|
|
798
|
+
id: c.name,
|
|
799
|
+
childrenCount: 0,
|
|
800
|
+
description: `${c.image} - ${c.status}`
|
|
801
|
+
} })) };
|
|
802
|
+
}
|
|
803
|
+
async listImages(_ctx) {
|
|
804
|
+
return { data: (await this.getImages()).map((i) => {
|
|
805
|
+
const safeName = this.sanitizeImageName(i.repository, i.tag);
|
|
806
|
+
return this.buildEntry(joinURL("/docker/images", safeName), { meta: {
|
|
807
|
+
kind: KINDS.DOCKER_IMAGE,
|
|
808
|
+
id: `${i.repository}:${i.tag}`,
|
|
809
|
+
childrenCount: 0
|
|
810
|
+
} });
|
|
811
|
+
}) };
|
|
812
|
+
}
|
|
813
|
+
async listNetworks(_ctx) {
|
|
814
|
+
return { data: [] };
|
|
815
|
+
}
|
|
816
|
+
async listProjects(_ctx) {
|
|
817
|
+
return { data: [] };
|
|
818
|
+
}
|
|
819
|
+
async listShares(_ctx) {
|
|
820
|
+
return { data: (await this.getShares()).map((s) => this.buildEntry(joinURL("/shares", s.name), { meta: {
|
|
821
|
+
kind: KINDS.SHARED_FOLDER,
|
|
822
|
+
id: s.name,
|
|
823
|
+
childrenCount: 0,
|
|
824
|
+
description: s.path
|
|
825
|
+
} })) };
|
|
826
|
+
}
|
|
827
|
+
async listPackages(_ctx) {
|
|
828
|
+
return { data: (await this.getPackages()).map((p) => {
|
|
829
|
+
const updateStr = p.has_update ? " (update available)" : "";
|
|
830
|
+
return this.buildEntry(joinURL("/packages", p.id), { meta: {
|
|
831
|
+
kind: KINDS.PACKAGE,
|
|
832
|
+
id: p.id,
|
|
833
|
+
childrenCount: 0,
|
|
834
|
+
description: `${p.name} v${p.version} - ${p.status}${updateStr}`
|
|
835
|
+
} });
|
|
836
|
+
}) };
|
|
837
|
+
}
|
|
838
|
+
async listBackup(_ctx) {
|
|
839
|
+
const tasks = await this.getBackupTasks();
|
|
840
|
+
return { data: [this.buildEntry("/backup/tasks", { meta: {
|
|
841
|
+
childrenCount: tasks.length,
|
|
842
|
+
description: "Hyper Backup tasks"
|
|
843
|
+
} })] };
|
|
844
|
+
}
|
|
845
|
+
async listBackupTasks(_ctx) {
|
|
846
|
+
return { data: (await this.getBackupTasks()).map((t) => {
|
|
847
|
+
const lastRunDate = t.last_run ? t.last_run.split("T")[0] : "never";
|
|
848
|
+
return this.buildEntry(joinURL("/backup/tasks", t.name), { meta: {
|
|
849
|
+
kind: KINDS.BACKUP_TASK,
|
|
850
|
+
id: t.name,
|
|
851
|
+
childrenCount: 0,
|
|
852
|
+
description: `${t.status} - last run ${lastRunDate}`
|
|
853
|
+
} });
|
|
854
|
+
}) };
|
|
855
|
+
}
|
|
856
|
+
async listSystemLeaf(_ctx) {
|
|
857
|
+
if (![
|
|
858
|
+
"status",
|
|
859
|
+
"utilization",
|
|
860
|
+
"hardware"
|
|
861
|
+
].includes(_ctx.params.item)) throw new AFSNotFoundError(joinURL("/system", _ctx.params.item));
|
|
862
|
+
return { data: [] };
|
|
863
|
+
}
|
|
864
|
+
async listDiskLeaf(ctx) {
|
|
865
|
+
if (!(await this.getDisks()).find((d) => d.id === ctx.params.diskId)) throw new AFSNotFoundError(joinURL("/storage/disks", ctx.params.diskId));
|
|
866
|
+
return { data: [] };
|
|
867
|
+
}
|
|
868
|
+
async listPoolLeaf(ctx) {
|
|
869
|
+
if (!(await this.getPools()).find((p) => p.id === ctx.params.poolId)) throw new AFSNotFoundError(joinURL("/storage/pools", ctx.params.poolId));
|
|
870
|
+
return { data: [] };
|
|
871
|
+
}
|
|
872
|
+
async listVolumeLeaf(ctx) {
|
|
873
|
+
if (!(await this.getVolumes()).find((v) => v.id === ctx.params.volumeId)) throw new AFSNotFoundError(joinURL("/storage/volumes", ctx.params.volumeId));
|
|
874
|
+
return { data: [] };
|
|
875
|
+
}
|
|
876
|
+
async listContainerLeaf(ctx) {
|
|
877
|
+
if (!(await this.getContainers()).find((c) => c.name === ctx.params.containerName)) throw new AFSNotFoundError(joinURL("/docker/containers", ctx.params.containerName));
|
|
878
|
+
return { data: [] };
|
|
879
|
+
}
|
|
880
|
+
async listImageLeaf(ctx) {
|
|
881
|
+
if (!(await this.getImages()).find((i) => this.sanitizeImageName(i.repository, i.tag) === ctx.params.imageName)) throw new AFSNotFoundError(joinURL("/docker/images", ctx.params.imageName));
|
|
882
|
+
return { data: [] };
|
|
883
|
+
}
|
|
884
|
+
async listShareLeaf(ctx) {
|
|
885
|
+
if (!(await this.getShares()).find((s) => s.name === ctx.params.shareName)) throw new AFSNotFoundError(joinURL("/shares", ctx.params.shareName));
|
|
886
|
+
return { data: [] };
|
|
887
|
+
}
|
|
888
|
+
async listPackageLeaf(ctx) {
|
|
889
|
+
if (!(await this.getPackages()).find((p) => p.id === ctx.params.packageName)) throw new AFSNotFoundError(joinURL("/packages", ctx.params.packageName));
|
|
890
|
+
return { data: [] };
|
|
891
|
+
}
|
|
892
|
+
async listBackupTaskLeaf(ctx) {
|
|
893
|
+
if (!(await this.getBackupTasks()).find((t) => t.name === ctx.params.taskName)) throw new AFSNotFoundError(joinURL("/backup/tasks", ctx.params.taskName));
|
|
894
|
+
return { data: [] };
|
|
895
|
+
}
|
|
896
|
+
async readRoot(_ctx) {
|
|
897
|
+
const info = await this.getSystemInfo();
|
|
898
|
+
return this.buildEntry("/", {
|
|
899
|
+
content: `model: "${info.model}"\nversion: "${info.version}"\nserial: "${info.serial}"\nuptime_seconds: ${info.uptime}`,
|
|
900
|
+
meta: {
|
|
901
|
+
kind: KINDS.NAS,
|
|
902
|
+
model: info.model,
|
|
903
|
+
version: info.version,
|
|
904
|
+
serial: info.serial,
|
|
905
|
+
childrenCount: 6,
|
|
906
|
+
description: `Synology ${info.model} running DSM ${info.version}`
|
|
907
|
+
}
|
|
908
|
+
});
|
|
909
|
+
}
|
|
910
|
+
async readSystem(_ctx) {
|
|
911
|
+
return this.buildEntry("/system", { meta: {
|
|
912
|
+
kind: KINDS.SYSTEM_GROUP,
|
|
913
|
+
childrenCount: 3,
|
|
914
|
+
description: "System health and resource monitoring"
|
|
915
|
+
} });
|
|
916
|
+
}
|
|
917
|
+
async readSystemStatus(_ctx) {
|
|
918
|
+
const info = await this.getSystemInfo();
|
|
919
|
+
return this.buildEntry("/system/status", {
|
|
920
|
+
content: formatSystemStatus(info),
|
|
921
|
+
meta: {
|
|
922
|
+
kind: KINDS.SYSTEM_INFO,
|
|
923
|
+
childrenCount: 0,
|
|
924
|
+
description: "System health status summary"
|
|
925
|
+
}
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
async readSystemUtilization(_ctx) {
|
|
929
|
+
const util = await this.getUtilization();
|
|
930
|
+
return this.buildEntry("/system/utilization", {
|
|
931
|
+
content: formatSystemUtilization(util),
|
|
932
|
+
meta: {
|
|
933
|
+
kind: KINDS.SYSTEM_INFO,
|
|
934
|
+
childrenCount: 0,
|
|
935
|
+
description: "CPU, RAM, disk I/O, network statistics"
|
|
936
|
+
}
|
|
937
|
+
});
|
|
938
|
+
}
|
|
939
|
+
async readSystemHardware(_ctx) {
|
|
940
|
+
const info = await this.getSystemInfo();
|
|
941
|
+
return this.buildEntry("/system/hardware", {
|
|
942
|
+
content: formatHardwareInfo(info),
|
|
943
|
+
meta: {
|
|
944
|
+
kind: KINDS.SYSTEM_INFO,
|
|
945
|
+
childrenCount: 0,
|
|
946
|
+
description: "Temperature, fan speed, power status"
|
|
947
|
+
}
|
|
948
|
+
});
|
|
949
|
+
}
|
|
950
|
+
async readStorage(_ctx) {
|
|
951
|
+
return this.buildEntry("/storage", { meta: {
|
|
952
|
+
kind: KINDS.STORAGE_GROUP,
|
|
953
|
+
childrenCount: 3,
|
|
954
|
+
description: "Physical disks, storage pools, and volumes"
|
|
955
|
+
} });
|
|
956
|
+
}
|
|
957
|
+
async readStorageDisks(_ctx) {
|
|
958
|
+
const disks = await this.getDisks();
|
|
959
|
+
return this.buildEntry("/storage/disks", { meta: {
|
|
960
|
+
childrenCount: disks.length,
|
|
961
|
+
description: "Physical disk drives"
|
|
962
|
+
} });
|
|
963
|
+
}
|
|
964
|
+
async readDisk(ctx) {
|
|
965
|
+
const disk = (await this.getDisks()).find((d) => d.id === ctx.params.diskId);
|
|
966
|
+
if (!disk) throw new AFSNotFoundError(joinURL("/storage/disks", ctx.params.diskId));
|
|
967
|
+
return this.buildEntry(joinURL("/storage/disks", disk.id), {
|
|
968
|
+
content: formatDiskInfo(disk),
|
|
969
|
+
meta: {
|
|
970
|
+
kind: KINDS.DISK,
|
|
971
|
+
id: disk.id,
|
|
972
|
+
childrenCount: 0,
|
|
973
|
+
description: `${disk.model} - ${formatBytes(disk.size_total)} - ${disk.smart_status || disk.status}`
|
|
974
|
+
}
|
|
975
|
+
});
|
|
976
|
+
}
|
|
977
|
+
async readStoragePools(_ctx) {
|
|
978
|
+
const pools = await this.getPools();
|
|
979
|
+
return this.buildEntry("/storage/pools", { meta: {
|
|
980
|
+
childrenCount: pools.length,
|
|
981
|
+
description: "RAID storage pools"
|
|
982
|
+
} });
|
|
983
|
+
}
|
|
984
|
+
async readPool(ctx) {
|
|
985
|
+
const pool = (await this.getPools()).find((p) => p.id === ctx.params.poolId);
|
|
986
|
+
if (!pool) throw new AFSNotFoundError(joinURL("/storage/pools", ctx.params.poolId));
|
|
987
|
+
return this.buildEntry(joinURL("/storage/pools", pool.id), {
|
|
988
|
+
content: formatPoolInfo(pool),
|
|
989
|
+
meta: {
|
|
990
|
+
kind: KINDS.STORAGE_POOL,
|
|
991
|
+
id: pool.id,
|
|
992
|
+
childrenCount: 0,
|
|
993
|
+
description: `${pool.raidType || "unknown"} pool - ${pool.status}`
|
|
994
|
+
}
|
|
995
|
+
});
|
|
996
|
+
}
|
|
997
|
+
async readStorageVolumes(_ctx) {
|
|
998
|
+
const volumes = await this.getVolumes();
|
|
999
|
+
return this.buildEntry("/storage/volumes", { meta: {
|
|
1000
|
+
childrenCount: volumes.length,
|
|
1001
|
+
description: "Logical volumes"
|
|
1002
|
+
} });
|
|
1003
|
+
}
|
|
1004
|
+
async readVolume(ctx) {
|
|
1005
|
+
const volume = (await this.getVolumes()).find((v) => v.id === ctx.params.volumeId);
|
|
1006
|
+
if (!volume) throw new AFSNotFoundError(joinURL("/storage/volumes", ctx.params.volumeId));
|
|
1007
|
+
const usedPercent = Math.round(volume.size.used / volume.size.total * 100);
|
|
1008
|
+
return this.buildEntry(joinURL("/storage/volumes", volume.id), {
|
|
1009
|
+
content: formatVolumeInfo(volume),
|
|
1010
|
+
meta: {
|
|
1011
|
+
kind: KINDS.VOLUME,
|
|
1012
|
+
id: volume.id,
|
|
1013
|
+
childrenCount: 0,
|
|
1014
|
+
description: `${volume.fs_type} - ${usedPercent}% used`
|
|
1015
|
+
}
|
|
1016
|
+
});
|
|
1017
|
+
}
|
|
1018
|
+
async readDocker(_ctx) {
|
|
1019
|
+
return this.buildEntry("/docker", { meta: {
|
|
1020
|
+
kind: KINDS.DOCKER_GROUP,
|
|
1021
|
+
childrenCount: 4,
|
|
1022
|
+
description: "Docker containers, images, networks, and projects"
|
|
1023
|
+
} });
|
|
1024
|
+
}
|
|
1025
|
+
async readDockerContainers(_ctx) {
|
|
1026
|
+
return this.buildEntry("/docker/containers", { meta: {
|
|
1027
|
+
childrenCount: -1,
|
|
1028
|
+
description: "Running and stopped containers"
|
|
1029
|
+
} });
|
|
1030
|
+
}
|
|
1031
|
+
async readContainer(ctx) {
|
|
1032
|
+
const container = (await this.getContainers()).find((c) => c.name === ctx.params.containerName);
|
|
1033
|
+
if (!container) throw new AFSNotFoundError(joinURL("/docker/containers", ctx.params.containerName));
|
|
1034
|
+
return this.buildEntry(joinURL("/docker/containers", container.name), {
|
|
1035
|
+
content: formatContainerInfo(container),
|
|
1036
|
+
meta: {
|
|
1037
|
+
kind: KINDS.CONTAINER,
|
|
1038
|
+
id: container.name,
|
|
1039
|
+
childrenCount: 0,
|
|
1040
|
+
description: `${container.image} - ${container.status}`
|
|
1041
|
+
}
|
|
1042
|
+
});
|
|
1043
|
+
}
|
|
1044
|
+
async readDockerImages(_ctx) {
|
|
1045
|
+
return this.buildEntry("/docker/images", { meta: {
|
|
1046
|
+
childrenCount: -1,
|
|
1047
|
+
description: "Local Docker images"
|
|
1048
|
+
} });
|
|
1049
|
+
}
|
|
1050
|
+
async readImage(ctx) {
|
|
1051
|
+
const image = (await this.getImages()).find((i) => this.sanitizeImageName(i.repository, i.tag) === ctx.params.imageName);
|
|
1052
|
+
if (!image) throw new AFSNotFoundError(joinURL("/docker/images", ctx.params.imageName));
|
|
1053
|
+
const safeName = this.sanitizeImageName(image.repository, image.tag);
|
|
1054
|
+
return this.buildEntry(joinURL("/docker/images", safeName), {
|
|
1055
|
+
content: formatImageInfo(image),
|
|
1056
|
+
meta: {
|
|
1057
|
+
kind: KINDS.DOCKER_IMAGE,
|
|
1058
|
+
id: `${image.repository}:${image.tag}`,
|
|
1059
|
+
childrenCount: 0
|
|
1060
|
+
}
|
|
1061
|
+
});
|
|
1062
|
+
}
|
|
1063
|
+
async readDockerNetworks(_ctx) {
|
|
1064
|
+
return this.buildEntry("/docker/networks", { meta: {
|
|
1065
|
+
childrenCount: 0,
|
|
1066
|
+
description: "Docker networks"
|
|
1067
|
+
} });
|
|
1068
|
+
}
|
|
1069
|
+
async readDockerProjects(_ctx) {
|
|
1070
|
+
return this.buildEntry("/docker/projects", { meta: {
|
|
1071
|
+
childrenCount: 0,
|
|
1072
|
+
description: "Docker Compose projects (DSM 7.2+)"
|
|
1073
|
+
} });
|
|
1074
|
+
}
|
|
1075
|
+
async readSharesDir(_ctx) {
|
|
1076
|
+
const shares = await this.getShares();
|
|
1077
|
+
return this.buildEntry("/shares", { meta: {
|
|
1078
|
+
childrenCount: shares.length,
|
|
1079
|
+
description: "Shared folders"
|
|
1080
|
+
} });
|
|
1081
|
+
}
|
|
1082
|
+
async readShare(ctx) {
|
|
1083
|
+
const share = (await this.getShares()).find((s) => s.name === ctx.params.shareName);
|
|
1084
|
+
if (!share) throw new AFSNotFoundError(joinURL("/shares", ctx.params.shareName));
|
|
1085
|
+
return this.buildEntry(joinURL("/shares", share.name), {
|
|
1086
|
+
content: formatShareInfo(share),
|
|
1087
|
+
meta: {
|
|
1088
|
+
kind: KINDS.SHARED_FOLDER,
|
|
1089
|
+
id: share.name,
|
|
1090
|
+
childrenCount: 0,
|
|
1091
|
+
description: share.path
|
|
1092
|
+
}
|
|
1093
|
+
});
|
|
1094
|
+
}
|
|
1095
|
+
async readPackagesDir(_ctx) {
|
|
1096
|
+
const packages = await this.getPackages();
|
|
1097
|
+
return this.buildEntry("/packages", { meta: {
|
|
1098
|
+
childrenCount: packages.length,
|
|
1099
|
+
description: "Installed DSM packages"
|
|
1100
|
+
} });
|
|
1101
|
+
}
|
|
1102
|
+
async readPackage(ctx) {
|
|
1103
|
+
const pkg = (await this.getPackages()).find((p) => p.id === ctx.params.packageName);
|
|
1104
|
+
if (!pkg) throw new AFSNotFoundError(joinURL("/packages", ctx.params.packageName));
|
|
1105
|
+
const updateStr = pkg.has_update ? " (update available)" : "";
|
|
1106
|
+
return this.buildEntry(joinURL("/packages", pkg.id), {
|
|
1107
|
+
content: formatPackageInfo(pkg),
|
|
1108
|
+
meta: {
|
|
1109
|
+
kind: KINDS.PACKAGE,
|
|
1110
|
+
id: pkg.id,
|
|
1111
|
+
childrenCount: 0,
|
|
1112
|
+
description: `${pkg.name} v${pkg.version} - ${pkg.status}${updateStr}`
|
|
1113
|
+
}
|
|
1114
|
+
});
|
|
1115
|
+
}
|
|
1116
|
+
async readBackupDir(_ctx) {
|
|
1117
|
+
return this.buildEntry("/backup", { meta: {
|
|
1118
|
+
childrenCount: 1,
|
|
1119
|
+
description: "Backup management"
|
|
1120
|
+
} });
|
|
1121
|
+
}
|
|
1122
|
+
async readBackupTasksDir(_ctx) {
|
|
1123
|
+
const tasks = await this.getBackupTasks();
|
|
1124
|
+
return this.buildEntry("/backup/tasks", { meta: {
|
|
1125
|
+
childrenCount: tasks.length,
|
|
1126
|
+
description: "Hyper Backup tasks"
|
|
1127
|
+
} });
|
|
1128
|
+
}
|
|
1129
|
+
async readBackupTask(ctx) {
|
|
1130
|
+
const task = (await this.getBackupTasks()).find((t) => t.name === ctx.params.taskName);
|
|
1131
|
+
if (!task) throw new AFSNotFoundError(joinURL("/backup/tasks", ctx.params.taskName));
|
|
1132
|
+
const lastRunDate = task.last_run ? task.last_run.split("T")[0] : "never";
|
|
1133
|
+
return this.buildEntry(joinURL("/backup/tasks", task.name), {
|
|
1134
|
+
content: formatBackupTaskInfo(task),
|
|
1135
|
+
meta: {
|
|
1136
|
+
kind: KINDS.BACKUP_TASK,
|
|
1137
|
+
id: task.name,
|
|
1138
|
+
childrenCount: 0,
|
|
1139
|
+
description: `${task.status} - last run ${lastRunDate}`
|
|
1140
|
+
}
|
|
1141
|
+
});
|
|
1142
|
+
}
|
|
1143
|
+
async statHandler(ctx) {
|
|
1144
|
+
const entry = (await this.read(ctx.path)).data;
|
|
1145
|
+
if (!entry) throw new AFSNotFoundError(ctx.path);
|
|
1146
|
+
return { data: {
|
|
1147
|
+
id: entry.id,
|
|
1148
|
+
path: entry.path,
|
|
1149
|
+
meta: entry.meta
|
|
1150
|
+
} };
|
|
1151
|
+
}
|
|
1152
|
+
async explainRoot(_ctx) {
|
|
1153
|
+
const info = await this.getSystemInfo();
|
|
1154
|
+
return {
|
|
1155
|
+
format: "markdown",
|
|
1156
|
+
content: `# Synology NAS: ${info.model}
|
|
1157
|
+
|
|
1158
|
+
- **Model**: ${info.model}
|
|
1159
|
+
- **DSM Version**: ${info.version}
|
|
1160
|
+
- **Serial**: ${info.serial}
|
|
1161
|
+
- **Uptime**: ${Math.floor(info.uptime / 3600)}h ${Math.floor(info.uptime % 3600 / 60)}m
|
|
1162
|
+
|
|
1163
|
+
## Navigation
|
|
1164
|
+
|
|
1165
|
+
- \`/system/\` - System health and resource monitoring
|
|
1166
|
+
- \`/storage/\` - Physical disks, storage pools, and volumes
|
|
1167
|
+
- \`/docker/\` - Docker containers, images, networks
|
|
1168
|
+
- \`/shares/\` - Shared folders
|
|
1169
|
+
- \`/packages/\` - Installed DSM packages
|
|
1170
|
+
- \`/backup/\` - Hyper Backup task management
|
|
1171
|
+
`
|
|
1172
|
+
};
|
|
1173
|
+
}
|
|
1174
|
+
async explainSystem(_ctx) {
|
|
1175
|
+
return {
|
|
1176
|
+
format: "markdown",
|
|
1177
|
+
content: `# System Monitoring
|
|
1178
|
+
|
|
1179
|
+
System health and resource monitoring for the Synology NAS.
|
|
1180
|
+
|
|
1181
|
+
## Sub-items
|
|
1182
|
+
|
|
1183
|
+
- \`/system/status\` - Overall system health (temperature, uptime)
|
|
1184
|
+
- \`/system/utilization\` - Real-time CPU, RAM, disk I/O, network stats
|
|
1185
|
+
- \`/system/hardware\` - Hardware sensors (temperature, fan speed, power)
|
|
1186
|
+
|
|
1187
|
+
Data sourced from SYNO.Core.System and SYNO.Core.System.Utilization APIs.
|
|
1188
|
+
`
|
|
1189
|
+
};
|
|
1190
|
+
}
|
|
1191
|
+
async explainStorage(_ctx) {
|
|
1192
|
+
return {
|
|
1193
|
+
format: "markdown",
|
|
1194
|
+
content: `# Storage Overview
|
|
1195
|
+
|
|
1196
|
+
Hierarchical view of physical storage:
|
|
1197
|
+
|
|
1198
|
+
\`\`\`
|
|
1199
|
+
Physical Disks --> Storage Pools (RAID) --> Volumes (filesystem)
|
|
1200
|
+
\`\`\`
|
|
1201
|
+
|
|
1202
|
+
## Sub-items
|
|
1203
|
+
|
|
1204
|
+
- \`/storage/disks/\` - Physical disk drives with SMART status
|
|
1205
|
+
- \`/storage/pools/\` - RAID storage pools
|
|
1206
|
+
- \`/storage/volumes/\` - Logical volumes with usage info
|
|
1207
|
+
|
|
1208
|
+
Data sourced from SYNO.Storage.CGI.Storage API.
|
|
1209
|
+
`
|
|
1210
|
+
};
|
|
1211
|
+
}
|
|
1212
|
+
async explainDocker(_ctx) {
|
|
1213
|
+
const containers = await this.getContainers();
|
|
1214
|
+
const running = containers.filter((c) => c.status === "running").length;
|
|
1215
|
+
return {
|
|
1216
|
+
format: "markdown",
|
|
1217
|
+
content: `# Docker Environment
|
|
1218
|
+
|
|
1219
|
+
Container management for the Synology NAS.
|
|
1220
|
+
|
|
1221
|
+
- **Running containers**: ${running}
|
|
1222
|
+
- **Stopped containers**: ${containers.length - running}
|
|
1223
|
+
|
|
1224
|
+
## Sub-items
|
|
1225
|
+
|
|
1226
|
+
- \`/docker/containers/\` - Container list and details
|
|
1227
|
+
- \`/docker/images/\` - Local Docker images
|
|
1228
|
+
- \`/docker/networks/\` - Docker networks
|
|
1229
|
+
- \`/docker/projects/\` - Docker Compose projects (DSM 7.2+)
|
|
1230
|
+
|
|
1231
|
+
## Actions
|
|
1232
|
+
|
|
1233
|
+
Containers support the following actions via exec:
|
|
1234
|
+
- \`start\` - Start a stopped container
|
|
1235
|
+
- \`stop\` - Stop a running container
|
|
1236
|
+
- \`restart\` - Restart a container
|
|
1237
|
+
`
|
|
1238
|
+
};
|
|
1239
|
+
}
|
|
1240
|
+
async explainGeneric(ctx) {
|
|
1241
|
+
const entry = (await this.read(ctx.path)).data;
|
|
1242
|
+
if (!entry) throw new AFSNotFoundError(ctx.path);
|
|
1243
|
+
const name = ctx.path.split("/").filter(Boolean).pop() || ctx.path;
|
|
1244
|
+
const desc = entry.meta?.description || "No description available";
|
|
1245
|
+
const kind = entry.meta?.kind || "unknown";
|
|
1246
|
+
let content = `# ${name}\n\n- **Path**: \`${ctx.path}\`\n- **Kind**: ${kind}\n- **Description**: ${desc}\n`;
|
|
1247
|
+
if (entry.content && typeof entry.content === "string") content += `\n## Data\n\n\`\`\`yaml\n${entry.content}\n\`\`\`\n`;
|
|
1248
|
+
return {
|
|
1249
|
+
format: "markdown",
|
|
1250
|
+
content
|
|
1251
|
+
};
|
|
1252
|
+
}
|
|
1253
|
+
async searchHandler(ctx, query, options) {
|
|
1254
|
+
const searchPath = ctx.params.path ? `/${ctx.params.path}` : "/";
|
|
1255
|
+
const limit = options?.limit ?? 100;
|
|
1256
|
+
const searchQuery = query.toLowerCase();
|
|
1257
|
+
const entries = [];
|
|
1258
|
+
const collectEntries = async (listPath) => {
|
|
1259
|
+
try {
|
|
1260
|
+
const result = await this.list(listPath, { maxDepth: 10 });
|
|
1261
|
+
for (const entry of result.data) entries.push(entry);
|
|
1262
|
+
} catch {}
|
|
1263
|
+
};
|
|
1264
|
+
await collectEntries(searchPath);
|
|
1265
|
+
return { data: entries.filter((entry) => {
|
|
1266
|
+
if (!searchQuery) return true;
|
|
1267
|
+
return [
|
|
1268
|
+
entry.path.split("/").filter(Boolean).pop() || "",
|
|
1269
|
+
String(entry.meta?.id || entry.id || ""),
|
|
1270
|
+
String(entry.meta?.description || ""),
|
|
1271
|
+
String(entry.meta?.kind || "")
|
|
1272
|
+
].join(" ").toLowerCase().includes(searchQuery);
|
|
1273
|
+
}).slice(0, limit) };
|
|
1274
|
+
}
|
|
1275
|
+
async listContainerActions(ctx) {
|
|
1276
|
+
if (!(await this.getContainers()).find((c) => c.name === ctx.params.containerName)) throw new AFSNotFoundError(joinURL("/docker/containers", ctx.params.containerName));
|
|
1277
|
+
return { data: [
|
|
1278
|
+
{
|
|
1279
|
+
id: "start",
|
|
1280
|
+
path: joinURL(ctx.path, "start"),
|
|
1281
|
+
summary: "Start the container",
|
|
1282
|
+
meta: {
|
|
1283
|
+
kind: "afs:executable",
|
|
1284
|
+
kinds: ["afs:executable", "afs:node"],
|
|
1285
|
+
severity: "boundary",
|
|
1286
|
+
inputSchema: {
|
|
1287
|
+
type: "object",
|
|
1288
|
+
properties: {}
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
},
|
|
1292
|
+
{
|
|
1293
|
+
id: "stop",
|
|
1294
|
+
path: joinURL(ctx.path, "stop"),
|
|
1295
|
+
summary: "Stop the container",
|
|
1296
|
+
meta: {
|
|
1297
|
+
kind: "afs:executable",
|
|
1298
|
+
kinds: ["afs:executable", "afs:node"],
|
|
1299
|
+
severity: "boundary",
|
|
1300
|
+
inputSchema: {
|
|
1301
|
+
type: "object",
|
|
1302
|
+
properties: {}
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
},
|
|
1306
|
+
{
|
|
1307
|
+
id: "restart",
|
|
1308
|
+
path: joinURL(ctx.path, "restart"),
|
|
1309
|
+
summary: "Restart the container",
|
|
1310
|
+
meta: {
|
|
1311
|
+
kind: "afs:executable",
|
|
1312
|
+
kinds: ["afs:executable", "afs:node"],
|
|
1313
|
+
severity: "boundary",
|
|
1314
|
+
inputSchema: {
|
|
1315
|
+
type: "object",
|
|
1316
|
+
properties: {}
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
] };
|
|
1321
|
+
}
|
|
1322
|
+
async startContainerAction(ctx, _args) {
|
|
1323
|
+
if (!(await this.getContainers()).find((c) => c.name === ctx.params.containerName)) throw new AFSNotFoundError(joinURL("/docker/containers", ctx.params.containerName));
|
|
1324
|
+
try {
|
|
1325
|
+
await this.client.request({
|
|
1326
|
+
api: "SYNO.Docker.Container",
|
|
1327
|
+
method: "start",
|
|
1328
|
+
version: 1,
|
|
1329
|
+
extra: { name: ctx.params.containerName }
|
|
1330
|
+
});
|
|
1331
|
+
} catch (error) {
|
|
1332
|
+
if (error instanceof AFSError) return {
|
|
1333
|
+
success: false,
|
|
1334
|
+
error: {
|
|
1335
|
+
code: "CONTAINER_ERROR",
|
|
1336
|
+
message: error.message
|
|
1337
|
+
}
|
|
1338
|
+
};
|
|
1339
|
+
throw error;
|
|
1340
|
+
}
|
|
1341
|
+
this.invalidateContainerCache();
|
|
1342
|
+
return {
|
|
1343
|
+
success: true,
|
|
1344
|
+
data: {
|
|
1345
|
+
container: ctx.params.containerName,
|
|
1346
|
+
action: "start",
|
|
1347
|
+
status: "running"
|
|
1348
|
+
}
|
|
1349
|
+
};
|
|
1350
|
+
}
|
|
1351
|
+
async stopContainerAction(ctx, _args) {
|
|
1352
|
+
if (!(await this.getContainers()).find((c) => c.name === ctx.params.containerName)) throw new AFSNotFoundError(joinURL("/docker/containers", ctx.params.containerName));
|
|
1353
|
+
try {
|
|
1354
|
+
await this.client.request({
|
|
1355
|
+
api: "SYNO.Docker.Container",
|
|
1356
|
+
method: "stop",
|
|
1357
|
+
version: 1,
|
|
1358
|
+
extra: { name: ctx.params.containerName }
|
|
1359
|
+
});
|
|
1360
|
+
} catch (error) {
|
|
1361
|
+
if (error instanceof AFSError) return {
|
|
1362
|
+
success: false,
|
|
1363
|
+
error: {
|
|
1364
|
+
code: "CONTAINER_ERROR",
|
|
1365
|
+
message: error.message
|
|
1366
|
+
}
|
|
1367
|
+
};
|
|
1368
|
+
throw error;
|
|
1369
|
+
}
|
|
1370
|
+
this.invalidateContainerCache();
|
|
1371
|
+
return {
|
|
1372
|
+
success: true,
|
|
1373
|
+
data: {
|
|
1374
|
+
container: ctx.params.containerName,
|
|
1375
|
+
action: "stop",
|
|
1376
|
+
status: "stopped"
|
|
1377
|
+
}
|
|
1378
|
+
};
|
|
1379
|
+
}
|
|
1380
|
+
async restartContainerAction(ctx, _args) {
|
|
1381
|
+
if (!(await this.getContainers()).find((c) => c.name === ctx.params.containerName)) throw new AFSNotFoundError(joinURL("/docker/containers", ctx.params.containerName));
|
|
1382
|
+
try {
|
|
1383
|
+
await this.client.request({
|
|
1384
|
+
api: "SYNO.Docker.Container",
|
|
1385
|
+
method: "restart",
|
|
1386
|
+
version: 1,
|
|
1387
|
+
extra: { name: ctx.params.containerName }
|
|
1388
|
+
});
|
|
1389
|
+
} catch (error) {
|
|
1390
|
+
if (error instanceof AFSError) return {
|
|
1391
|
+
success: false,
|
|
1392
|
+
error: {
|
|
1393
|
+
code: "CONTAINER_ERROR",
|
|
1394
|
+
message: error.message
|
|
1395
|
+
}
|
|
1396
|
+
};
|
|
1397
|
+
throw error;
|
|
1398
|
+
}
|
|
1399
|
+
this.invalidateContainerCache();
|
|
1400
|
+
return {
|
|
1401
|
+
success: true,
|
|
1402
|
+
data: {
|
|
1403
|
+
container: ctx.params.containerName,
|
|
1404
|
+
action: "restart",
|
|
1405
|
+
status: "running"
|
|
1406
|
+
}
|
|
1407
|
+
};
|
|
1408
|
+
}
|
|
1409
|
+
async listBackupTaskActions(ctx) {
|
|
1410
|
+
if (!(await this.getBackupTasks()).find((t) => t.name === ctx.params.taskName)) throw new AFSNotFoundError(joinURL("/backup/tasks", ctx.params.taskName));
|
|
1411
|
+
return { data: [{
|
|
1412
|
+
id: "run",
|
|
1413
|
+
path: joinURL(ctx.path, "run"),
|
|
1414
|
+
summary: "Trigger backup execution",
|
|
1415
|
+
meta: {
|
|
1416
|
+
kind: "afs:executable",
|
|
1417
|
+
kinds: ["afs:executable", "afs:node"],
|
|
1418
|
+
severity: "boundary",
|
|
1419
|
+
inputSchema: {
|
|
1420
|
+
type: "object",
|
|
1421
|
+
properties: {}
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
}] };
|
|
1425
|
+
}
|
|
1426
|
+
async runBackupTaskAction(ctx, _args) {
|
|
1427
|
+
if (!(await this.getBackupTasks()).find((t) => t.name === ctx.params.taskName)) throw new AFSNotFoundError(joinURL("/backup/tasks", ctx.params.taskName));
|
|
1428
|
+
try {
|
|
1429
|
+
await this.client.request({
|
|
1430
|
+
api: "SYNO.Backup.Task",
|
|
1431
|
+
method: "run",
|
|
1432
|
+
version: 1,
|
|
1433
|
+
extra: { name: ctx.params.taskName }
|
|
1434
|
+
});
|
|
1435
|
+
} catch (error) {
|
|
1436
|
+
if (error instanceof AFSError) return {
|
|
1437
|
+
success: false,
|
|
1438
|
+
error: {
|
|
1439
|
+
code: "BACKUP_ERROR",
|
|
1440
|
+
message: error.message
|
|
1441
|
+
}
|
|
1442
|
+
};
|
|
1443
|
+
throw error;
|
|
1444
|
+
}
|
|
1445
|
+
this.invalidateBackupTaskCache();
|
|
1446
|
+
return {
|
|
1447
|
+
success: true,
|
|
1448
|
+
data: {
|
|
1449
|
+
task: ctx.params.taskName,
|
|
1450
|
+
action: "run",
|
|
1451
|
+
status: "running"
|
|
1452
|
+
}
|
|
1453
|
+
};
|
|
1454
|
+
}
|
|
1455
|
+
async listPackageActions(ctx) {
|
|
1456
|
+
if (!(await this.getPackages()).find((p) => p.id === ctx.params.packageName)) throw new AFSNotFoundError(joinURL("/packages", ctx.params.packageName));
|
|
1457
|
+
return { data: [{
|
|
1458
|
+
id: "start",
|
|
1459
|
+
path: joinURL(ctx.path, "start"),
|
|
1460
|
+
summary: "Start the package",
|
|
1461
|
+
meta: {
|
|
1462
|
+
kind: "afs:executable",
|
|
1463
|
+
kinds: ["afs:executable", "afs:node"],
|
|
1464
|
+
severity: "boundary",
|
|
1465
|
+
inputSchema: {
|
|
1466
|
+
type: "object",
|
|
1467
|
+
properties: {}
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
}, {
|
|
1471
|
+
id: "stop",
|
|
1472
|
+
path: joinURL(ctx.path, "stop"),
|
|
1473
|
+
summary: "Stop the package",
|
|
1474
|
+
meta: {
|
|
1475
|
+
kind: "afs:executable",
|
|
1476
|
+
kinds: ["afs:executable", "afs:node"],
|
|
1477
|
+
severity: "boundary",
|
|
1478
|
+
inputSchema: {
|
|
1479
|
+
type: "object",
|
|
1480
|
+
properties: {}
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
}] };
|
|
1484
|
+
}
|
|
1485
|
+
async startPackageAction(ctx, _args) {
|
|
1486
|
+
if (!(await this.getPackages()).find((p) => p.id === ctx.params.packageName)) throw new AFSNotFoundError(joinURL("/packages", ctx.params.packageName));
|
|
1487
|
+
try {
|
|
1488
|
+
await this.client.request({
|
|
1489
|
+
api: "SYNO.Core.Package",
|
|
1490
|
+
method: "start",
|
|
1491
|
+
version: 1,
|
|
1492
|
+
extra: { id: ctx.params.packageName }
|
|
1493
|
+
});
|
|
1494
|
+
} catch (error) {
|
|
1495
|
+
if (error instanceof AFSError) return {
|
|
1496
|
+
success: false,
|
|
1497
|
+
error: {
|
|
1498
|
+
code: "PACKAGE_ERROR",
|
|
1499
|
+
message: error.message
|
|
1500
|
+
}
|
|
1501
|
+
};
|
|
1502
|
+
throw error;
|
|
1503
|
+
}
|
|
1504
|
+
this.invalidatePackageCache();
|
|
1505
|
+
return {
|
|
1506
|
+
success: true,
|
|
1507
|
+
data: {
|
|
1508
|
+
package: ctx.params.packageName,
|
|
1509
|
+
action: "start",
|
|
1510
|
+
status: "running"
|
|
1511
|
+
}
|
|
1512
|
+
};
|
|
1513
|
+
}
|
|
1514
|
+
async stopPackageAction(ctx, _args) {
|
|
1515
|
+
if (!(await this.getPackages()).find((p) => p.id === ctx.params.packageName)) throw new AFSNotFoundError(joinURL("/packages", ctx.params.packageName));
|
|
1516
|
+
try {
|
|
1517
|
+
await this.client.request({
|
|
1518
|
+
api: "SYNO.Core.Package",
|
|
1519
|
+
method: "stop",
|
|
1520
|
+
version: 1,
|
|
1521
|
+
extra: { id: ctx.params.packageName }
|
|
1522
|
+
});
|
|
1523
|
+
} catch (error) {
|
|
1524
|
+
if (error instanceof AFSError) return {
|
|
1525
|
+
success: false,
|
|
1526
|
+
error: {
|
|
1527
|
+
code: "PACKAGE_ERROR",
|
|
1528
|
+
message: error.message
|
|
1529
|
+
}
|
|
1530
|
+
};
|
|
1531
|
+
throw error;
|
|
1532
|
+
}
|
|
1533
|
+
this.invalidatePackageCache();
|
|
1534
|
+
return {
|
|
1535
|
+
success: true,
|
|
1536
|
+
data: {
|
|
1537
|
+
package: ctx.params.packageName,
|
|
1538
|
+
action: "stop",
|
|
1539
|
+
status: "stopped"
|
|
1540
|
+
}
|
|
1541
|
+
};
|
|
1542
|
+
}
|
|
1543
|
+
async destroy() {
|
|
1544
|
+
await this.client.destroy();
|
|
1545
|
+
}
|
|
1546
|
+
};
|
|
1547
|
+
__decorate([Read("/.meta/.capabilities")], AFSSynology.prototype, "readCapabilities", null);
|
|
1548
|
+
__decorate([Meta("/")], AFSSynology.prototype, "metaRoot", null);
|
|
1549
|
+
__decorate([Meta("/system")], AFSSynology.prototype, "metaSystem", null);
|
|
1550
|
+
__decorate([Meta("/system/:item")], AFSSynology.prototype, "metaSystemItem", null);
|
|
1551
|
+
__decorate([Meta("/storage")], AFSSynology.prototype, "metaStorage", null);
|
|
1552
|
+
__decorate([Meta("/storage/disks")], AFSSynology.prototype, "metaDisks", null);
|
|
1553
|
+
__decorate([Meta("/storage/disks/:diskId")], AFSSynology.prototype, "metaDisk", null);
|
|
1554
|
+
__decorate([Meta("/storage/pools")], AFSSynology.prototype, "metaPools", null);
|
|
1555
|
+
__decorate([Meta("/storage/pools/:poolId")], AFSSynology.prototype, "metaPool", null);
|
|
1556
|
+
__decorate([Meta("/storage/volumes")], AFSSynology.prototype, "metaVolumes", null);
|
|
1557
|
+
__decorate([Meta("/storage/volumes/:volumeId")], AFSSynology.prototype, "metaVolume", null);
|
|
1558
|
+
__decorate([Meta("/docker")], AFSSynology.prototype, "metaDocker", null);
|
|
1559
|
+
__decorate([Meta("/docker/containers")], AFSSynology.prototype, "metaContainers", null);
|
|
1560
|
+
__decorate([Meta("/docker/containers/:containerName")], AFSSynology.prototype, "metaContainer", null);
|
|
1561
|
+
__decorate([Meta("/docker/images")], AFSSynology.prototype, "metaImages", null);
|
|
1562
|
+
__decorate([Meta("/docker/images/:imageName")], AFSSynology.prototype, "metaImage", null);
|
|
1563
|
+
__decorate([Meta("/docker/networks")], AFSSynology.prototype, "metaNetworks", null);
|
|
1564
|
+
__decorate([Meta("/docker/projects")], AFSSynology.prototype, "metaProjects", null);
|
|
1565
|
+
__decorate([Meta("/shares")], AFSSynology.prototype, "metaShares", null);
|
|
1566
|
+
__decorate([Meta("/shares/:shareName")], AFSSynology.prototype, "metaShare", null);
|
|
1567
|
+
__decorate([Meta("/packages")], AFSSynology.prototype, "metaPackages", null);
|
|
1568
|
+
__decorate([Meta("/packages/:packageName")], AFSSynology.prototype, "metaPackage", null);
|
|
1569
|
+
__decorate([Meta("/backup")], AFSSynology.prototype, "metaBackup", null);
|
|
1570
|
+
__decorate([Meta("/backup/tasks")], AFSSynology.prototype, "metaBackupTasks", null);
|
|
1571
|
+
__decorate([Meta("/backup/tasks/:taskName")], AFSSynology.prototype, "metaBackupTask", null);
|
|
1572
|
+
__decorate([List("/")], AFSSynology.prototype, "listRoot", null);
|
|
1573
|
+
__decorate([List("/system")], AFSSynology.prototype, "listSystem", null);
|
|
1574
|
+
__decorate([List("/storage")], AFSSynology.prototype, "listStorage", null);
|
|
1575
|
+
__decorate([List("/storage/disks")], AFSSynology.prototype, "listDisks", null);
|
|
1576
|
+
__decorate([List("/storage/pools")], AFSSynology.prototype, "listPools", null);
|
|
1577
|
+
__decorate([List("/storage/volumes")], AFSSynology.prototype, "listVolumes", null);
|
|
1578
|
+
__decorate([List("/docker")], AFSSynology.prototype, "listDocker", null);
|
|
1579
|
+
__decorate([List("/docker/containers")], AFSSynology.prototype, "listContainers", null);
|
|
1580
|
+
__decorate([List("/docker/images")], AFSSynology.prototype, "listImages", null);
|
|
1581
|
+
__decorate([List("/docker/networks")], AFSSynology.prototype, "listNetworks", null);
|
|
1582
|
+
__decorate([List("/docker/projects")], AFSSynology.prototype, "listProjects", null);
|
|
1583
|
+
__decorate([List("/shares")], AFSSynology.prototype, "listShares", null);
|
|
1584
|
+
__decorate([List("/packages")], AFSSynology.prototype, "listPackages", null);
|
|
1585
|
+
__decorate([List("/backup")], AFSSynology.prototype, "listBackup", null);
|
|
1586
|
+
__decorate([List("/backup/tasks")], AFSSynology.prototype, "listBackupTasks", null);
|
|
1587
|
+
__decorate([List("/system/:item")], AFSSynology.prototype, "listSystemLeaf", null);
|
|
1588
|
+
__decorate([List("/storage/disks/:diskId")], AFSSynology.prototype, "listDiskLeaf", null);
|
|
1589
|
+
__decorate([List("/storage/pools/:poolId")], AFSSynology.prototype, "listPoolLeaf", null);
|
|
1590
|
+
__decorate([List("/storage/volumes/:volumeId")], AFSSynology.prototype, "listVolumeLeaf", null);
|
|
1591
|
+
__decorate([List("/docker/containers/:containerName")], AFSSynology.prototype, "listContainerLeaf", null);
|
|
1592
|
+
__decorate([List("/docker/images/:imageName")], AFSSynology.prototype, "listImageLeaf", null);
|
|
1593
|
+
__decorate([List("/shares/:shareName")], AFSSynology.prototype, "listShareLeaf", null);
|
|
1594
|
+
__decorate([List("/packages/:packageName")], AFSSynology.prototype, "listPackageLeaf", null);
|
|
1595
|
+
__decorate([List("/backup/tasks/:taskName")], AFSSynology.prototype, "listBackupTaskLeaf", null);
|
|
1596
|
+
__decorate([Read("/")], AFSSynology.prototype, "readRoot", null);
|
|
1597
|
+
__decorate([Read("/system")], AFSSynology.prototype, "readSystem", null);
|
|
1598
|
+
__decorate([Read("/system/status")], AFSSynology.prototype, "readSystemStatus", null);
|
|
1599
|
+
__decorate([Read("/system/utilization")], AFSSynology.prototype, "readSystemUtilization", null);
|
|
1600
|
+
__decorate([Read("/system/hardware")], AFSSynology.prototype, "readSystemHardware", null);
|
|
1601
|
+
__decorate([Read("/storage")], AFSSynology.prototype, "readStorage", null);
|
|
1602
|
+
__decorate([Read("/storage/disks")], AFSSynology.prototype, "readStorageDisks", null);
|
|
1603
|
+
__decorate([Read("/storage/disks/:diskId")], AFSSynology.prototype, "readDisk", null);
|
|
1604
|
+
__decorate([Read("/storage/pools")], AFSSynology.prototype, "readStoragePools", null);
|
|
1605
|
+
__decorate([Read("/storage/pools/:poolId")], AFSSynology.prototype, "readPool", null);
|
|
1606
|
+
__decorate([Read("/storage/volumes")], AFSSynology.prototype, "readStorageVolumes", null);
|
|
1607
|
+
__decorate([Read("/storage/volumes/:volumeId")], AFSSynology.prototype, "readVolume", null);
|
|
1608
|
+
__decorate([Read("/docker")], AFSSynology.prototype, "readDocker", null);
|
|
1609
|
+
__decorate([Read("/docker/containers")], AFSSynology.prototype, "readDockerContainers", null);
|
|
1610
|
+
__decorate([Read("/docker/containers/:containerName")], AFSSynology.prototype, "readContainer", null);
|
|
1611
|
+
__decorate([Read("/docker/images")], AFSSynology.prototype, "readDockerImages", null);
|
|
1612
|
+
__decorate([Read("/docker/images/:imageName")], AFSSynology.prototype, "readImage", null);
|
|
1613
|
+
__decorate([Read("/docker/networks")], AFSSynology.prototype, "readDockerNetworks", null);
|
|
1614
|
+
__decorate([Read("/docker/projects")], AFSSynology.prototype, "readDockerProjects", null);
|
|
1615
|
+
__decorate([Read("/shares")], AFSSynology.prototype, "readSharesDir", null);
|
|
1616
|
+
__decorate([Read("/shares/:shareName")], AFSSynology.prototype, "readShare", null);
|
|
1617
|
+
__decorate([Read("/packages")], AFSSynology.prototype, "readPackagesDir", null);
|
|
1618
|
+
__decorate([Read("/packages/:packageName")], AFSSynology.prototype, "readPackage", null);
|
|
1619
|
+
__decorate([Read("/backup")], AFSSynology.prototype, "readBackupDir", null);
|
|
1620
|
+
__decorate([Read("/backup/tasks")], AFSSynology.prototype, "readBackupTasksDir", null);
|
|
1621
|
+
__decorate([Read("/backup/tasks/:taskName")], AFSSynology.prototype, "readBackupTask", null);
|
|
1622
|
+
__decorate([
|
|
1623
|
+
Stat("/"),
|
|
1624
|
+
Stat("/system"),
|
|
1625
|
+
Stat("/system/:item"),
|
|
1626
|
+
Stat("/storage"),
|
|
1627
|
+
Stat("/storage/disks"),
|
|
1628
|
+
Stat("/storage/disks/:diskId"),
|
|
1629
|
+
Stat("/storage/pools"),
|
|
1630
|
+
Stat("/storage/pools/:poolId"),
|
|
1631
|
+
Stat("/storage/volumes"),
|
|
1632
|
+
Stat("/storage/volumes/:volumeId"),
|
|
1633
|
+
Stat("/docker"),
|
|
1634
|
+
Stat("/docker/containers"),
|
|
1635
|
+
Stat("/docker/containers/:containerName"),
|
|
1636
|
+
Stat("/docker/images"),
|
|
1637
|
+
Stat("/docker/images/:imageName"),
|
|
1638
|
+
Stat("/docker/networks"),
|
|
1639
|
+
Stat("/docker/projects"),
|
|
1640
|
+
Stat("/shares"),
|
|
1641
|
+
Stat("/shares/:shareName"),
|
|
1642
|
+
Stat("/packages"),
|
|
1643
|
+
Stat("/packages/:packageName"),
|
|
1644
|
+
Stat("/backup"),
|
|
1645
|
+
Stat("/backup/tasks"),
|
|
1646
|
+
Stat("/backup/tasks/:taskName")
|
|
1647
|
+
], AFSSynology.prototype, "statHandler", null);
|
|
1648
|
+
__decorate([Explain("/")], AFSSynology.prototype, "explainRoot", null);
|
|
1649
|
+
__decorate([Explain("/system")], AFSSynology.prototype, "explainSystem", null);
|
|
1650
|
+
__decorate([Explain("/storage")], AFSSynology.prototype, "explainStorage", null);
|
|
1651
|
+
__decorate([Explain("/docker")], AFSSynology.prototype, "explainDocker", null);
|
|
1652
|
+
__decorate([
|
|
1653
|
+
Explain("/system/:item"),
|
|
1654
|
+
Explain("/storage/disks"),
|
|
1655
|
+
Explain("/storage/disks/:diskId"),
|
|
1656
|
+
Explain("/storage/pools"),
|
|
1657
|
+
Explain("/storage/pools/:poolId"),
|
|
1658
|
+
Explain("/storage/volumes"),
|
|
1659
|
+
Explain("/storage/volumes/:volumeId"),
|
|
1660
|
+
Explain("/docker/containers"),
|
|
1661
|
+
Explain("/docker/containers/:containerName"),
|
|
1662
|
+
Explain("/docker/images"),
|
|
1663
|
+
Explain("/docker/images/:imageName"),
|
|
1664
|
+
Explain("/docker/networks"),
|
|
1665
|
+
Explain("/docker/projects"),
|
|
1666
|
+
Explain("/shares"),
|
|
1667
|
+
Explain("/shares/:shareName"),
|
|
1668
|
+
Explain("/packages"),
|
|
1669
|
+
Explain("/packages/:packageName"),
|
|
1670
|
+
Explain("/backup"),
|
|
1671
|
+
Explain("/backup/tasks"),
|
|
1672
|
+
Explain("/backup/tasks/:taskName")
|
|
1673
|
+
], AFSSynology.prototype, "explainGeneric", null);
|
|
1674
|
+
__decorate([Search("/"), Search("/:path*")], AFSSynology.prototype, "searchHandler", null);
|
|
1675
|
+
__decorate([Actions("/docker/containers/:containerName")], AFSSynology.prototype, "listContainerActions", null);
|
|
1676
|
+
__decorate([Actions.Exec("/docker/containers/:containerName", "start")], AFSSynology.prototype, "startContainerAction", null);
|
|
1677
|
+
__decorate([Actions.Exec("/docker/containers/:containerName", "stop")], AFSSynology.prototype, "stopContainerAction", null);
|
|
1678
|
+
__decorate([Actions.Exec("/docker/containers/:containerName", "restart")], AFSSynology.prototype, "restartContainerAction", null);
|
|
1679
|
+
__decorate([Actions("/backup/tasks/:taskName")], AFSSynology.prototype, "listBackupTaskActions", null);
|
|
1680
|
+
__decorate([Actions.Exec("/backup/tasks/:taskName", "run")], AFSSynology.prototype, "runBackupTaskAction", null);
|
|
1681
|
+
__decorate([Actions("/packages/:packageName")], AFSSynology.prototype, "listPackageActions", null);
|
|
1682
|
+
__decorate([Actions.Exec("/packages/:packageName", "start")], AFSSynology.prototype, "startPackageAction", null);
|
|
1683
|
+
__decorate([Actions.Exec("/packages/:packageName", "stop")], AFSSynology.prototype, "stopPackageAction", null);
|
|
1684
|
+
|
|
1685
|
+
//#endregion
|
|
1686
|
+
export { AFSSynology };
|
|
1687
|
+
//# sourceMappingURL=synology-afs.mjs.map
|