@ninedeploy/sdk 0.10.5 → 0.16.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.
Files changed (129) hide show
  1. package/LICENSE +21 -21
  2. package/dist/apiPath.d.ts +5 -0
  3. package/dist/apiPath.d.ts.map +1 -0
  4. package/dist/apiPath.js +18 -0
  5. package/dist/apiPath.js.map +1 -0
  6. package/dist/client.d.ts +29 -0
  7. package/dist/client.d.ts.map +1 -0
  8. package/dist/client.js +51 -0
  9. package/dist/client.js.map +1 -0
  10. package/dist/databaseImport.d.ts +138 -0
  11. package/dist/databaseImport.d.ts.map +1 -0
  12. package/dist/databaseImport.js +126 -0
  13. package/dist/databaseImport.js.map +1 -0
  14. package/dist/errors.js +1 -1
  15. package/dist/errors.js.map +1 -1
  16. package/dist/index.d.ts +12 -1973
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +9 -641
  19. package/dist/index.js.map +1 -1
  20. package/dist/manifest.d.ts.map +1 -1
  21. package/dist/manifest.js +25 -21
  22. package/dist/manifest.js.map +1 -1
  23. package/dist/resources/auth.d.ts +282 -0
  24. package/dist/resources/auth.d.ts.map +1 -0
  25. package/dist/resources/auth.js +111 -0
  26. package/dist/resources/auth.js.map +1 -0
  27. package/dist/resources/databases.d.ts +178 -0
  28. package/dist/resources/databases.d.ts.map +1 -0
  29. package/dist/resources/databases.js +82 -0
  30. package/dist/resources/databases.js.map +1 -0
  31. package/dist/resources/deploys.d.ts +125 -0
  32. package/dist/resources/deploys.d.ts.map +1 -0
  33. package/dist/resources/deploys.js +46 -0
  34. package/dist/resources/deploys.js.map +1 -0
  35. package/dist/resources/domains.d.ts +101 -0
  36. package/dist/resources/domains.d.ts.map +1 -0
  37. package/dist/resources/domains.js +28 -0
  38. package/dist/resources/domains.js.map +1 -0
  39. package/dist/resources/env.d.ts +55 -0
  40. package/dist/resources/env.d.ts.map +1 -0
  41. package/dist/resources/env.js +33 -0
  42. package/dist/resources/env.js.map +1 -0
  43. package/dist/resources/logs.d.ts +27 -0
  44. package/dist/resources/logs.d.ts.map +1 -0
  45. package/dist/resources/logs.js +18 -0
  46. package/dist/resources/logs.js.map +1 -0
  47. package/dist/resources/network.d.ts +134 -0
  48. package/dist/resources/network.d.ts.map +1 -0
  49. package/dist/resources/network.js +33 -0
  50. package/dist/resources/network.js.map +1 -0
  51. package/dist/resources/plugins.d.ts +42 -0
  52. package/dist/resources/plugins.d.ts.map +1 -0
  53. package/dist/resources/plugins.js +22 -0
  54. package/dist/resources/plugins.js.map +1 -0
  55. package/dist/resources/servers.d.ts +130 -0
  56. package/dist/resources/servers.d.ts.map +1 -0
  57. package/dist/resources/servers.js +37 -0
  58. package/dist/resources/servers.js.map +1 -0
  59. package/dist/resources/services.d.ts +185 -0
  60. package/dist/resources/services.d.ts.map +1 -0
  61. package/dist/resources/services.js +83 -0
  62. package/dist/resources/services.js.map +1 -0
  63. package/dist/resources/settings.d.ts +312 -0
  64. package/dist/resources/settings.d.ts.map +1 -0
  65. package/dist/resources/settings.js +85 -0
  66. package/dist/resources/settings.js.map +1 -0
  67. package/dist/resources/sources.d.ts +87 -0
  68. package/dist/resources/sources.d.ts.map +1 -0
  69. package/dist/resources/sources.js +39 -0
  70. package/dist/resources/sources.js.map +1 -0
  71. package/dist/resources/system.d.ts +240 -0
  72. package/dist/resources/system.d.ts.map +1 -0
  73. package/dist/resources/system.js +86 -0
  74. package/dist/resources/system.js.map +1 -0
  75. package/dist/resources/templates.d.ts +36 -0
  76. package/dist/resources/templates.d.ts.map +1 -0
  77. package/dist/resources/templates.js +17 -0
  78. package/dist/resources/templates.js.map +1 -0
  79. package/dist/resources/terminals.d.ts +60 -0
  80. package/dist/resources/terminals.d.ts.map +1 -0
  81. package/dist/resources/terminals.js +35 -0
  82. package/dist/resources/terminals.js.map +1 -0
  83. package/dist/resources/traefik.d.ts +84 -0
  84. package/dist/resources/traefik.d.ts.map +1 -0
  85. package/dist/resources/traefik.js +35 -0
  86. package/dist/resources/traefik.js.map +1 -0
  87. package/dist/resources/volumes.d.ts +114 -0
  88. package/dist/resources/volumes.d.ts.map +1 -0
  89. package/dist/resources/volumes.js +47 -0
  90. package/dist/resources/volumes.js.map +1 -0
  91. package/dist/terminal.d.ts +98 -0
  92. package/dist/terminal.d.ts.map +1 -0
  93. package/dist/terminal.js +180 -0
  94. package/dist/terminal.js.map +1 -0
  95. package/dist/transport.d.ts +61 -0
  96. package/dist/transport.d.ts.map +1 -0
  97. package/dist/transport.js +90 -0
  98. package/dist/transport.js.map +1 -0
  99. package/dist/types.d.ts +539 -0
  100. package/dist/types.d.ts.map +1 -0
  101. package/dist/types.js +5 -0
  102. package/dist/types.js.map +1 -0
  103. package/package.json +5 -4
  104. package/src/apiPath.ts +23 -0
  105. package/src/client.ts +70 -0
  106. package/src/databaseImport.ts +174 -0
  107. package/src/errors.ts +27 -0
  108. package/src/index.ts +118 -0
  109. package/src/manifest.ts +414 -0
  110. package/src/resources/auth.ts +357 -0
  111. package/src/resources/databases.ts +227 -0
  112. package/src/resources/deploys.ts +161 -0
  113. package/src/resources/domains.ts +142 -0
  114. package/src/resources/env.ts +84 -0
  115. package/src/resources/logs.ts +48 -0
  116. package/src/resources/network.ts +126 -0
  117. package/src/resources/plugins.ts +52 -0
  118. package/src/resources/servers.ts +141 -0
  119. package/src/resources/services.ts +250 -0
  120. package/src/resources/settings.ts +275 -0
  121. package/src/resources/sources.ts +131 -0
  122. package/src/resources/system.ts +281 -0
  123. package/src/resources/templates.ts +55 -0
  124. package/src/resources/terminals.ts +112 -0
  125. package/src/resources/traefik.ts +116 -0
  126. package/src/resources/volumes.ts +138 -0
  127. package/src/terminal.ts +254 -0
  128. package/src/transport.ts +154 -0
  129. package/src/types.ts +588 -0
@@ -0,0 +1,281 @@
1
+ // Client surface: system, health, about, dashboard, stats, topology, activity, alerts, metricHistory, buildCache, housekeeping, doctor, demo.
2
+ // Split out of the former single-file SDK entry (src/index.ts); the public
3
+ // surface is re-exported unchanged from there.
4
+ import type {
5
+ ActivityEntry,
6
+ AlertRule,
7
+ CreateAlertRuleInput,
8
+ DoctorFixRequestInput,
9
+ DoctorFixResponse,
10
+ DoctorReport,
11
+ DemoSeedResult,
12
+ DockerResources,
13
+ MetricSeries,
14
+ StatsSnapshot,
15
+ TopologyGraph,
16
+ UpdateCheckResult,
17
+ SelfUpdateStatus,
18
+ AutoPruneConfigUpdateInput,
19
+ AutoPruneStatus,
20
+ AutoPruneRunResult,
21
+ PanelBackupObject,
22
+ PanelBackupRestore,
23
+ PanelBackupSettingsPatch,
24
+ PanelBackupStatus,
25
+ } from '@ninedeploy/schemas';
26
+ import type { HealthStatus, ImageInfo, PruneImagesInput, PruneImagesResult } from '../types.js';
27
+ import type { Transport } from '../transport.js';
28
+
29
+ export interface SystemApi {
30
+ system: {
31
+ resources: () => Promise<DockerResources>;
32
+ pruneImages: () => Promise<{ ok: boolean }>;
33
+ exportUrl: () => string;
34
+ /** Latest-release check (admin). updateAvailable is null when offline/disabled. */
35
+ updateCheck: (force?: boolean) => Promise<UpdateCheckResult>;
36
+ /** State of a panel-initiated upgrade (idle/running/success/failed/unsupported). */
37
+ updateStatus: () => Promise<SelfUpdateStatus>;
38
+ /**
39
+ * Start upgrading the panel itself to an exact release tag; the updater survives the restart it performs.
40
+ * Refused with 409 `deploys_in_flight` while deployments are executing unless `force` is set (r572).
41
+ */
42
+ updateStart: (version: string, opts?: { force?: boolean }) => Promise<{ ok: boolean }>;
43
+ /** Recent docker daemon events (single-shot, for the Docker dashboard feed). */
44
+ dockerEvents: (minutes?: number) => Promise<{ events: Array<{ time: string; type: string; action: string; name: string }> }>;
45
+ /**
46
+ * Panel self-backup (0.12, operator-only): the `/system/export` archive sealed with a
47
+ * recovery passphrase and written to a backup destination on a schedule.
48
+ */
49
+ panelBackup: {
50
+ /** Settings, last run, next run (passphrase never returned — only `hasPassphrase`). */
51
+ get: () => Promise<PanelBackupStatus>;
52
+ /** Omitted fields keep their stored value; `passphrase` omitted = keep the stored one. */
53
+ update: (input: PanelBackupSettingsPatch) => Promise<PanelBackupStatus>;
54
+ /** "Back up now": starts a run in the background (409 while one is running); poll `get()`. */
55
+ run: () => Promise<{ ok: boolean; started: boolean }>;
56
+ /** Panel backups in a destination (default: the configured one), newest first. */
57
+ list: (destinationId?: number) => Promise<{ destinationId: number; items: PanelBackupObject[] }>;
58
+ /**
59
+ * DESTRUCTIVE: replaces this panel's database, master key, .env and Traefik config with
60
+ * the backup's. `confirm` must repeat the object's file name. Restart the panel afterwards.
61
+ */
62
+ restore: (input: PanelBackupRestore) => Promise<{ ok: boolean; message: string; meta: unknown; backupPath: string }>;
63
+ };
64
+ };
65
+ health: () => Promise<HealthStatus>;
66
+ about: {
67
+ get: () => Promise<{
68
+ name: string; version: string; description: string; license: string; repo: string; docs: string;
69
+ techStack: Array<{ category: string; items: string[] }>;
70
+ changelog: Array<{ version: string; date: string; title: string; changes: string[] }>;
71
+ /** Instance counts are only returned for authenticated requests. */
72
+ stats?: { services: number; databases: number; deployments: number; users: number };
73
+ }>;
74
+ };
75
+ dashboard: {
76
+ get: () => Promise<{
77
+ stats: { services: number; databases: number; deployments: number; domains: number; webhooks: number; running: number; stopped: number; errored: number; dbRunning: number; /** Whole-node count — operators only, null for members (r470). */ containers: number | null };
78
+ health: Array<{ serviceId: number; name: string; slug: string; type: string; status: string; healthy: boolean | null; responseMs: number | null; port: number | null; runtimeId: string | null; commitSha: string | null; lastDeploy: string | null }>;
79
+ recentDeploys: Array<{ id: number; serviceId: number; serviceName: string; status: string; commitSha: string | null; message: string | null; trigger: string; finishedAt: string | null; createdAt: string }>;
80
+ }>;
81
+ };
82
+ stats: {
83
+ snapshot: () => Promise<StatsSnapshot>;
84
+ metrics: (serviceId: number, opts?: { kind?: 'cpu' | 'memory'; minutes?: number }) => Promise<MetricSeries>;
85
+ };
86
+ topology: {
87
+ get: () => Promise<TopologyGraph>;
88
+ };
89
+ activity: {
90
+ /**
91
+ * Filter + paginate the audit trail. Omitted filters return the newest page.
92
+ * `entity` matches the bare name and `<name> #<id>`; `serviceId` (with the
93
+ * service name as `entity`) narrows to one service's trail.
94
+ */
95
+ list: (query?: { entity?: string; serviceId?: number; action?: string; userId?: number; before?: number }) => Promise<{ entries: ActivityEntry[]; nextCursor: number | null }>;
96
+ };
97
+ alerts: {
98
+ list: () => Promise<AlertRule[]>;
99
+ create: (input: CreateAlertRuleInput) => Promise<AlertRule>;
100
+ update: (id: number, input: Partial<CreateAlertRuleInput>) => Promise<AlertRule>;
101
+ remove: (id: number) => Promise<void>;
102
+ };
103
+ metricHistory: {
104
+ /** Current configuration snapshot (backend choice, enabled flag, retention, last-flush). */
105
+ get: () => Promise<{
106
+ enabled: boolean;
107
+ backend: 'builtin' | 'prometheus' | 'influxdb';
108
+ events: string[];
109
+ retentionDays: number;
110
+ lastFlush: { ts: number; backend: string; count: number };
111
+ }>;
112
+ /** Run the built-in backend's retention sweep. Returns the row count trimmed. */
113
+ flush: () => Promise<{ ok: boolean; backend: 'builtin'; deleted: number }>;
114
+ };
115
+ buildCache: {
116
+ /** Per-backend counters + merged totals. G-01 PR-A. */
117
+ stats: () => Promise<{
118
+ backends: Array<{
119
+ name: string;
120
+ entries: number;
121
+ totalBytes: number;
122
+ hits: number;
123
+ misses: number;
124
+ stores: number;
125
+ evictions: number;
126
+ }>;
127
+ totals: {
128
+ entries: number;
129
+ totalBytes: number;
130
+ hits: number;
131
+ misses: number;
132
+ stores: number;
133
+ evictions: number;
134
+ };
135
+ }>;
136
+ };
137
+ housekeeping: {
138
+ getAutoPrune: () => Promise<AutoPruneStatus>;
139
+ updateAutoPrune: (input: AutoPruneConfigUpdateInput) => Promise<AutoPruneStatus>;
140
+ runPrune: () => Promise<AutoPruneRunResult>;
141
+ /**
142
+ * List every image on the host with repo / tag / size /
143
+ * created / dangling / inUse metadata. The companion to
144
+ * the auto-prune cron: this is what the operator
145
+ * inspects before `pruneImages({ keepLast: 5 })`.
146
+ */
147
+ listImages: () => Promise<{ images: ImageInfo[]; totalCount: number; totalBytes: number }>;
148
+ /**
149
+ * Prune images with operator-supplied filters. `dryRun`
150
+ * returns the candidate set without deleting. Refused
151
+ * when no filter is supplied (a naked prune would
152
+ * delete every unused image).
153
+ */
154
+ pruneImages: (input: PruneImagesInput) => Promise<PruneImagesResult>;
155
+ };
156
+ /** Host-wide analysis + guarded cleanup. */
157
+ doctor: {
158
+ scan: () => Promise<DoctorReport>;
159
+ fix: (input: DoctorFixRequestInput) => Promise<DoctorFixResponse>;
160
+ };
161
+ demo: {
162
+ seed: () => Promise<DemoSeedResult>;
163
+ };
164
+ }
165
+
166
+ export function systemApi(t: Transport): SystemApi {
167
+ const { get, send, request } = t;
168
+ return {
169
+ system: {
170
+ resources: () => get<DockerResources>('/v1/system/resources'),
171
+ pruneImages: () => send<{ ok: boolean }>('POST', '/v1/system/prune-images'),
172
+ updateCheck: (force) => get<UpdateCheckResult>(`/v1/system/update-check${force ? '?force=1' : ''}`),
173
+ updateStatus: () => get<SelfUpdateStatus>('/v1/system/update-status'),
174
+ updateStart: (version, opts) => send<{ ok: boolean }>('POST', '/v1/system/update-start', { version, ...opts }),
175
+ exportUrl: () => '/v1/system/export',
176
+ dockerEvents: (minutes) =>
177
+ get<{ events: Array<{ time: string; type: string; action: string; name: string }> }>(
178
+ `/v1/system/docker-events?minutes=${minutes ?? 60}`,
179
+ ),
180
+ panelBackup: {
181
+ get: () => get<PanelBackupStatus>('/v1/system/panel-backup'),
182
+ update: (input) => send<PanelBackupStatus>('PUT', '/v1/system/panel-backup', input),
183
+ run: () => send<{ ok: boolean; started: boolean }>('POST', '/v1/system/panel-backup/run'),
184
+ list: (destinationId) =>
185
+ get<{ destinationId: number; items: PanelBackupObject[] }>(
186
+ `/v1/system/panel-backup/remote${destinationId != null ? `?destinationId=${destinationId}` : ''}`,
187
+ ),
188
+ restore: (input) =>
189
+ send<{ ok: boolean; message: string; meta: unknown; backupPath: string }>('POST', '/v1/system/panel-backup/restore', input),
190
+ },
191
+ },
192
+ health: () => get<HealthStatus>('/health'),
193
+ about: {
194
+ get: () => get('/v1/about'),
195
+ },
196
+ dashboard: {
197
+ get: () => get('/v1/dashboard'),
198
+ },
199
+ stats: {
200
+ snapshot: () => get<StatsSnapshot>('/v1/stats'),
201
+ metrics: (serviceId, opts) =>
202
+ get<MetricSeries>(
203
+ `/v1/services/${serviceId}/metrics?kind=${opts?.kind ?? 'cpu'}&minutes=${opts?.minutes ?? 60}`,
204
+ ),
205
+ },
206
+ topology: {
207
+ get: () => get<TopologyGraph>('/v1/topology'),
208
+ },
209
+ activity: {
210
+ list: (query) => {
211
+ const parts: string[] = [];
212
+ if (query?.entity) parts.push(`entity=${encodeURIComponent(query.entity)}`);
213
+ if (query?.serviceId) parts.push(`serviceId=${query.serviceId}`);
214
+ if (query?.action) parts.push(`action=${encodeURIComponent(query.action)}`);
215
+ if (query?.userId) parts.push(`userId=${query.userId}`);
216
+ if (query?.before) parts.push(`before=${query.before}`);
217
+ const qs = parts.join('&');
218
+ return get<{ entries: ActivityEntry[]; nextCursor: number | null }>(`/v1/activity${qs ? `?${qs}` : ''}`);
219
+ },
220
+ },
221
+ alerts: {
222
+ list: () => get('/v1/alerts'),
223
+ create: (input) => send('POST', '/v1/alerts', input),
224
+ update: (id, input) => send('PATCH', `/v1/alerts/${id}`, input),
225
+ remove: async (id) => {
226
+ await request(`/v1/alerts/${id}`, { method: 'DELETE' });
227
+ },
228
+ },
229
+ metricHistory: {
230
+ get: () => get<{
231
+ enabled: boolean;
232
+ backend: 'builtin' | 'prometheus' | 'influxdb';
233
+ events: string[];
234
+ retentionDays: number;
235
+ lastFlush: { ts: number; backend: string; count: number };
236
+ }>('/v1/metric-history'),
237
+ flush: () => send<{ ok: boolean; backend: 'builtin'; deleted: number }>('POST', '/v1/metric-history/flush'),
238
+ },
239
+ buildCache: {
240
+ stats: () => get<{
241
+ backends: Array<{
242
+ name: string;
243
+ entries: number;
244
+ totalBytes: number;
245
+ hits: number;
246
+ misses: number;
247
+ stores: number;
248
+ evictions: number;
249
+ }>;
250
+ totals: {
251
+ entries: number;
252
+ totalBytes: number;
253
+ hits: number;
254
+ misses: number;
255
+ stores: number;
256
+ evictions: number;
257
+ };
258
+ }>('/v1/build-cache/stats'),
259
+ },
260
+ housekeeping: {
261
+ getAutoPrune: () => get<AutoPruneStatus>('/v1/housekeeping/prune/config'),
262
+ updateAutoPrune: (input) => send<AutoPruneStatus>('PATCH', '/v1/housekeeping/prune/config', input),
263
+ runPrune: () => send<AutoPruneRunResult>('POST', '/v1/housekeeping/prune'),
264
+ listImages: () =>
265
+ get<{ images: ImageInfo[]; totalCount: number; totalBytes: number }>('/v1/housekeeping/images'),
266
+ pruneImages: (input) =>
267
+ send<PruneImagesResult>('POST', '/v1/housekeeping/images/prune', input),
268
+ },
269
+ doctor: {
270
+ /** Full host scan: dead containers, orphan volumes/networks, row-vs-runtime
271
+ * desyncs, dangling images, disk pressure — with guarded repair actions. */
272
+ scan: () => get<DoctorReport>('/v1/doctor'),
273
+ /** Execute one finding's repair. The server re-scans and re-locates the
274
+ * finding first; a stale finding answers 409 instead of acting. */
275
+ fix: (input: DoctorFixRequestInput) => send<DoctorFixResponse>('POST', '/v1/doctor/fix', input),
276
+ },
277
+ demo: {
278
+ seed: () => send<DemoSeedResult>('POST', '/v1/demo/seed'),
279
+ },
280
+ };
281
+ }
@@ -0,0 +1,55 @@
1
+ // Client surface: templates.
2
+ // Split out of the former single-file SDK entry (src/index.ts); the public
3
+ // surface is re-exported unchanged from there.
4
+ import type {
5
+ DeployTemplateInput,
6
+ Template,
7
+ TemplateSummary,
8
+ CommunityTemplateListResult,
9
+ } from '@ninedeploy/schemas';
10
+ import type { TemplateDeployResult, TemplatePrepareResult } from '../types.js';
11
+ import type { Transport } from '../transport.js';
12
+
13
+ export interface TemplatesApi {
14
+ templates: {
15
+ list: () => Promise<TemplateSummary[]>;
16
+ get: (id: string) => Promise<Template>;
17
+ prepare: (id: string, input?: DeployTemplateInput) => Promise<TemplatePrepareResult>;
18
+ deploy: (id: string, input?: DeployTemplateInput) => Promise<TemplateDeployResult>;
19
+ /**
20
+ * Community contributions (G-13). The `list` call
21
+ * already merges community entries with the curated
22
+ * catalog; the dedicated `community` namespace lets
23
+ * the panel surface errors and the operator import
24
+ * a new template from a PR comment or a file.
25
+ */
26
+ community: {
27
+ list: () => Promise<CommunityTemplateListResult>;
28
+ import: (content: string, opts?: { replace?: boolean }) => Promise<{ ok: boolean; id: string; file: string; bytes: number }>;
29
+ remove: (id: string) => Promise<{ ok: boolean; id: string; removed: boolean }>;
30
+ };
31
+ };
32
+ }
33
+
34
+ export function templatesApi(t: Transport): TemplatesApi {
35
+ const { get, send } = t;
36
+ return {
37
+ templates: {
38
+ list: () => get<TemplateSummary[]>('/v1/templates'),
39
+ get: (id) => get<Template>(`/v1/templates/${encodeURIComponent(id)}`),
40
+ prepare: (id, input) => send<TemplatePrepareResult>('POST', `/v1/templates/${encodeURIComponent(id)}/prepare`, input ?? {}),
41
+ deploy: (id, input) => send<TemplateDeployResult>('POST', `/v1/templates/${encodeURIComponent(id)}/deploy`, input ?? {}),
42
+ community: {
43
+ list: () => get<CommunityTemplateListResult>('/v1/templates/community'),
44
+ import: (content, opts) =>
45
+ send<{ ok: boolean; id: string; file: string; bytes: number }>(
46
+ 'POST',
47
+ '/v1/templates/community/import',
48
+ { content, ...(opts ?? {}) },
49
+ ),
50
+ remove: (id) =>
51
+ send<{ ok: boolean; id: string; removed: boolean }>('DELETE', `/v1/templates/community/${encodeURIComponent(id)}`),
52
+ },
53
+ },
54
+ };
55
+ }
@@ -0,0 +1,112 @@
1
+ // Client surface: terminals, traffic, api.
2
+ // Split out of the former single-file SDK entry (src/index.ts); the public
3
+ // surface is re-exported unchanged from there.
4
+ import type {
5
+ CreateTerminalSessionInput,
6
+ ServiceTrafficSummary,
7
+ TerminalSession,
8
+ TerminalSessionCreated,
9
+ TerminalSessionList,
10
+ TerminalSettingsInput,
11
+ TerminalSettingsView,
12
+ TerminalTerminateResult,
13
+ TrafficSettingsUpdate,
14
+ TrafficSettingsView,
15
+ TrafficSummary,
16
+ } from '@ninedeploy/schemas';
17
+ import { apiGetUrl, type ApiQuery } from '../apiPath.js';
18
+ import { terminalAttachInfo, connectTerminal, type TerminalAttachSource, type WebSocketLocation, type TerminalAttachInfo, type TerminalHandlers, type TerminalConnectOptions, type TerminalConnection } from '../terminal.js';
19
+ import type { TerminalListQuery, TrafficQuery } from '../types.js';
20
+ import type { Transport } from '../transport.js';
21
+
22
+ export interface TerminalsApi {
23
+ // ── end 0.15 T4 ──
24
+ // ── 0.15 T6 surfaces ──
25
+ /**
26
+ * Interactive terminals (0.15, operator only). A session is created over
27
+ * HTTP and attached over a WebSocket with its single-use ticket (valid 30s).
28
+ * Host shells (`target.kind: 'host'`) are off by default and need an
29
+ * interactive session plus the account password in `password`.
30
+ */
31
+ terminals: {
32
+ create: (input: CreateTerminalSessionInput) => Promise<TerminalSessionCreated>;
33
+ /** History, newest first; page back with `before: list.nextBefore`. */
34
+ list: (query?: TerminalListQuery) => Promise<TerminalSessionList>;
35
+ get: (id: number) => Promise<TerminalSession>;
36
+ /** Close a live session (close 4410) or revoke a pending ticket. */
37
+ terminate: (id: number) => Promise<TerminalTerminateResult>;
38
+ settings: {
39
+ get: () => Promise<TerminalSettingsView>;
40
+ /** Partial. Turning host shells on needs `password` (step-up). */
41
+ set: (input: TerminalSettingsInput) => Promise<TerminalSettingsView>;
42
+ };
43
+ /** The WebSocket URL and subprotocols that attach a created session. */
44
+ attachInfo: (created: TerminalAttachSource, location?: WebSocketLocation) => TerminalAttachInfo;
45
+ /** Attach a created session over protocol v1 (see `connectTerminal`). */
46
+ connect: (created: TerminalAttachSource, handlers?: TerminalHandlers, options?: TerminalConnectOptions) => TerminalConnection;
47
+ };
48
+ /** Traffic analytics (0.15, opt-in). Settings and the summary are operator only. */
49
+ traffic: {
50
+ settings: {
51
+ get: () => Promise<TrafficSettingsView>;
52
+ /**
53
+ * Enabling or disabling recreates Traefik once (about 1–2s of refused
54
+ * connections). A browser behind Traefik may lose this response; read
55
+ * `settings.get()` again on a network error.
56
+ */
57
+ set: (input: TrafficSettingsUpdate) => Promise<TrafficSettingsView>;
58
+ };
59
+ summary: (query?: TrafficQuery) => Promise<TrafficSummary>;
60
+ /** One service's series (any seat on the service). Empty series, never 404, without data. */
61
+ service: (serviceId: number, query?: TrafficQuery) => Promise<ServiceTrafficSummary>;
62
+ };
63
+ // ── 0.15 T4 api ──
64
+ /**
65
+ * Raw access to the documented API (0.15, see `GET /v1/openapi.json`).
66
+ * Read-only on purpose: there is no generic write method. The generated MCP
67
+ * read tools call through here.
68
+ */
69
+ api: {
70
+ /**
71
+ * GET any `/v1/…` route. The path must match `^/v1/[A-Za-z0-9/_.-]+$` and
72
+ * may not contain a `..` segment; query values are URL-encoded and
73
+ * `undefined` values are dropped.
74
+ */
75
+ get: <T = unknown>(path: `/v1/${string}`, query?: ApiQuery) => Promise<T>;
76
+ };
77
+ }
78
+
79
+ export function terminalsApi(t: Transport): TerminalsApi {
80
+ const { get, send, baseUrl } = t;
81
+ return {
82
+ // ── end 0.15 T4 ──
83
+ // ── 0.15 T6 surfaces ──
84
+ terminals: {
85
+ create: (input) => send<TerminalSessionCreated>('POST', '/v1/terminals', input),
86
+ list: (query) => get<TerminalSessionList>(apiGetUrl('/v1/terminals', { ...query })),
87
+ get: (id) => get<TerminalSession>(`/v1/terminals/${id}`),
88
+ terminate: (id) => send<TerminalTerminateResult>('DELETE', `/v1/terminals/${id}`),
89
+ settings: {
90
+ get: () => get<TerminalSettingsView>('/v1/terminals/settings'),
91
+ set: (input) => send<TerminalSettingsView>('PUT', '/v1/terminals/settings', input),
92
+ },
93
+ attachInfo: (created, location) => terminalAttachInfo(baseUrl, created, location),
94
+ connect: (created, handlers, options) =>
95
+ connectTerminal(terminalAttachInfo(baseUrl, created, options?.location), handlers, options),
96
+ },
97
+ traffic: {
98
+ settings: {
99
+ get: () => get<TrafficSettingsView>('/v1/traffic/settings'),
100
+ set: (input) => send<TrafficSettingsView>('PUT', '/v1/traffic/settings', input),
101
+ },
102
+ summary: (query) => get<TrafficSummary>(apiGetUrl('/v1/traffic/summary', { ...query })),
103
+ service: (serviceId, query) =>
104
+ get<ServiceTrafficSummary>(apiGetUrl(`/v1/services/${serviceId}/traffic`, { ...query })),
105
+ },
106
+ // ── 0.15 T4 api ──
107
+ api: {
108
+ // async: a refused path rejects like any other failed call, never throws synchronously.
109
+ get: async <T = unknown>(path: `/v1/${string}`, query?: ApiQuery) => get<T>(apiGetUrl(path, query)),
110
+ },
111
+ };
112
+ }
@@ -0,0 +1,116 @@
1
+ // Client surface: traefik.
2
+ // Split out of the former single-file SDK entry (src/index.ts); the public
3
+ // surface is re-exported unchanged from there.
4
+ import type {
5
+ TraefikCertificate,
6
+ CertificateInventoryEntry,
7
+ CertificateInventoryReport,
8
+ TraefikInfo,
9
+ TraefikStatus,
10
+ TraefikCustomConfig,
11
+ TraefikCustomConfigApplied,
12
+ TraefikCustomConfigValidation,
13
+ CustomCertificate,
14
+ CustomCertificateReplace,
15
+ CustomCertificateSaved,
16
+ CustomCertificateUpload,
17
+ } from '@ninedeploy/schemas';
18
+ import type { TraefikVersionInfo, TraefikRoutingConfig } from '../types.js';
19
+ import type { Transport } from '../transport.js';
20
+
21
+ export interface TraefikApi {
22
+ traefik: {
23
+ get: () => Promise<TraefikInfo>;
24
+ status: () => Promise<TraefikStatus>;
25
+ certificates: () => Promise<TraefikCertificate[]>;
26
+ /**
27
+ * Richer certificate inventory (G-15). Same
28
+ * underlying data as `certificates()` but adds a
29
+ * `status` classification (valid / expiring-soon /
30
+ * expired), a `summary` block, and the full set of
31
+ * fields the Certificates page renders.
32
+ * `threshold` is the days-out cutoff for the
33
+ * `expiring-soon` bucket; default 30.
34
+ */
35
+ certificateInventory: (opts?: { threshold?: number }) => Promise<CertificateInventoryReport>;
36
+ /**
37
+ * Focused "expiring within N days" filter. Default
38
+ * 30. Used by the alert engine and by the panel's
39
+ * "About to expire" widget.
40
+ */
41
+ expiringCertificates: (opts?: { days?: number }) => Promise<{ threshold: number; count: number; certificates: CertificateInventoryEntry[] }>;
42
+ logs: (lines?: number) => Promise<{ logs: string[] }>;
43
+ restart: () => Promise<{ ok: boolean; message?: string }>;
44
+ /** r334: the server answers the host path of the copied acme.json. */
45
+ backupCerts: () => Promise<{ ok: boolean; backupPath: string }>;
46
+ /** r557: routers / services / middlewares from the dynamic config (operator-only). */
47
+ config: () => Promise<TraefikRoutingConfig>;
48
+ /** r557: running vs latest Traefik version (operator-only). */
49
+ version: () => Promise<TraefikVersionInfo>;
50
+ /**
51
+ * r557: pull the pinned Traefik image and recreate the proxy container
52
+ * (operator-only). An image pull outlasts the default request budget, so
53
+ * this call allows up to 10 minutes unless the client disabled timeouts.
54
+ */
55
+ update: () => Promise<{ ok: boolean; newVersion: string | null }>;
56
+ /**
57
+ * 0.14: the operator's custom dynamic config (`custom.yml`). `set` throws
58
+ * a NineDeployError whose `details` is `{ errors, warnings }` on a refusal.
59
+ */
60
+ customConfig: {
61
+ get: () => Promise<TraefikCustomConfig>;
62
+ /** No state change. */
63
+ validate: (content: string) => Promise<TraefikCustomConfigValidation>;
64
+ set: (content: string) => Promise<TraefikCustomConfigApplied>;
65
+ clear: () => Promise<{ ok: boolean; cleared: boolean }>;
66
+ };
67
+ /** 0.14: uploaded TLS certificates (operator-only). No response carries a private key. */
68
+ customCertificates: {
69
+ list: () => Promise<CustomCertificate[]>;
70
+ upload: (input: CustomCertificateUpload) => Promise<CustomCertificateSaved>;
71
+ replace: (id: number, input: CustomCertificateReplace) => Promise<CustomCertificateSaved>;
72
+ delete: (id: number) => Promise<{ ok: boolean }>;
73
+ };
74
+ };
75
+ }
76
+
77
+ export function traefikApi(t: Transport): TraefikApi {
78
+ const { get, send, request } = t;
79
+ return {
80
+ traefik: {
81
+ get: () => get<TraefikInfo>('/v1/traefik'),
82
+ status: () => get<TraefikStatus>('/v1/traefik/status'),
83
+ certificates: () => get<TraefikCertificate[]>('/v1/traefik/certificates'),
84
+ certificateInventory: (opts) =>
85
+ get<CertificateInventoryReport>(
86
+ // `!= null` (not truthiness): 0 ("only already-expired") is a real
87
+ // threshold and must reach the server, not silently become the
88
+ // server-side default of 30.
89
+ `/v1/traefik/certificates/inventory${opts?.threshold != null ? `?threshold=${opts.threshold}` : ''}`,
90
+ ),
91
+ expiringCertificates: (opts) =>
92
+ get<{ threshold: number; count: number; certificates: CertificateInventoryEntry[] }>(
93
+ `/v1/traefik/certificates/expiring${opts?.days != null ? `?days=${opts.days}` : ''}`,
94
+ ),
95
+ logs: (lines = 50) => get<{ logs: string[] }>(`/v1/traefik/logs?lines=${lines}`),
96
+ restart: () => send<{ ok: boolean; message: string }>('POST', '/v1/traefik/restart'),
97
+ backupCerts: () => send<{ ok: boolean; backupPath: string }>('POST', '/v1/traefik/backup-certs'),
98
+ config: () => get<TraefikRoutingConfig>('/v1/traefik/config'),
99
+ version: () => get<TraefikVersionInfo>('/v1/traefik/version'),
100
+ update: () => request<{ ok: boolean; newVersion: string | null }>('/v1/traefik/update', { method: 'POST' }, 10 * 60_000),
101
+ customConfig: {
102
+ get: () => get<TraefikCustomConfig>('/v1/traefik/custom-config'),
103
+ validate: (content) => send<TraefikCustomConfigValidation>('POST', '/v1/traefik/custom-config/validate', { content }),
104
+ set: (content) =>
105
+ request<TraefikCustomConfigApplied>('/v1/traefik/custom-config', { method: 'PUT', body: { content }, findings: true }),
106
+ clear: () => send<{ ok: boolean; cleared: boolean }>('DELETE', '/v1/traefik/custom-config'),
107
+ },
108
+ customCertificates: {
109
+ list: () => get<CustomCertificate[]>('/v1/traefik/certificates/custom'),
110
+ upload: (input) => send<CustomCertificateSaved>('POST', '/v1/traefik/certificates/custom', input),
111
+ replace: (id, input) => send<CustomCertificateSaved>('PUT', `/v1/traefik/certificates/custom/${id}`, input),
112
+ delete: (id) => send<{ ok: boolean }>('DELETE', `/v1/traefik/certificates/custom/${id}`),
113
+ },
114
+ },
115
+ };
116
+ }