dsh-plugin-container 0.0.0-stage → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +80 -2
- package/cordis.patch.yml +3 -0
- package/docs/ai-chat.png +0 -0
- package/docs/ai-new-session.png +0 -0
- package/docs/apple-container.png +0 -0
- package/docs/attach.png +0 -0
- package/docs/docker-console.png +0 -0
- package/docs/docker-overview.png +0 -0
- package/docs/docker-volumes.png +0 -0
- package/docs/image-architecture.png +0 -0
- package/docs/package-install.png +0 -0
- package/docs/provider-switch.png +0 -0
- package/docs/run-container.png +0 -0
- package/index.js +138 -0
- package/lib/client.js +3560 -0
- package/package.json +62 -4
- package/src/client/api.js +98 -0
- package/src/client/attach.jsx +134 -0
- package/src/client/components.jsx +320 -0
- package/src/client/hooks.js +161 -0
- package/src/client/i18n.js +502 -0
- package/src/client/icons.jsx +227 -0
- package/src/client/index.jsx +76 -0
- package/src/client/page.jsx +782 -0
- package/src/client/run-container.jsx +187 -0
- package/src/client/runs.js +182 -0
- package/src/client/store.js +91 -0
- package/src/client/styles.js +371 -0
- package/src/client/tabs/compose.jsx +94 -0
- package/src/client/tabs/console.jsx +211 -0
- package/src/client/tabs/containers.jsx +148 -0
- package/src/client/tabs/images.jsx +127 -0
- package/src/client/tabs/networks.jsx +91 -0
- package/src/client/tabs/overview.jsx +266 -0
- package/src/client/tabs/volumes.jsx +76 -0
- package/src/host/apple-cli.js +307 -0
- package/src/host/apple.js +471 -0
- package/src/host/attach.js +246 -0
- package/src/host/commands.js +235 -0
- package/src/host/docker-cli.js +405 -0
- package/src/host/jobs.js +245 -0
- package/src/host/parsers.js +484 -0
- package/src/host/runtime.js +285 -0
- package/src/host/service.js +836 -0
- package/src/host/tools.js +198 -0
- package/src/host/util.js +97 -0
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
/** Tolerant parsers for Docker, colima, and Apple container CLI output. */
|
|
2
|
+
import { formatBytes } from './util.js'
|
|
3
|
+
|
|
4
|
+
/** @param {unknown} value */
|
|
5
|
+
function textOf(value) {
|
|
6
|
+
return value === undefined || value === null ? '' : String(value)
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** @param {string} text @returns {any[]} */
|
|
10
|
+
export function parseNdjson(text) {
|
|
11
|
+
const items = []
|
|
12
|
+
for (const line of text.split(/\r?\n/u)) {
|
|
13
|
+
const trimmed = line.trim()
|
|
14
|
+
if (trimmed === '') continue
|
|
15
|
+
try {
|
|
16
|
+
const value = JSON.parse(trimmed)
|
|
17
|
+
if (Array.isArray(value)) items.push(...value)
|
|
18
|
+
else items.push(value)
|
|
19
|
+
} catch (error) {
|
|
20
|
+
throw new Error(`无法解析 JSON 行: ${error instanceof Error ? error.message : String(error)}`)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return items
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Parse a whole JSON document, an NDJSON stream, or a JSON array.
|
|
28
|
+
* @param {string} text
|
|
29
|
+
* @returns {any}
|
|
30
|
+
*/
|
|
31
|
+
export function parseJsonLoose(text) {
|
|
32
|
+
const trimmed = text.trim()
|
|
33
|
+
if (trimmed === '') return undefined
|
|
34
|
+
try {
|
|
35
|
+
return JSON.parse(trimmed)
|
|
36
|
+
} catch {
|
|
37
|
+
const rows = parseNdjson(trimmed)
|
|
38
|
+
return rows.length === 1 ? rows[0] : rows
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** @param {string} text */
|
|
43
|
+
export function firstLine(text) {
|
|
44
|
+
const line = text.split(/\r?\n/u).find((value) => value.trim() !== '')
|
|
45
|
+
return line === undefined ? '' : line.trim()
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** @param {string} text @param {number} [max] */
|
|
49
|
+
export function summarizeStderr(text, max = 2048) {
|
|
50
|
+
if (typeof text !== 'string' || text === '') return ''
|
|
51
|
+
if (text.length <= max) return text
|
|
52
|
+
return `…[truncated]\n${text.slice(-max)}`
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** @param {unknown} value */
|
|
56
|
+
export function parseLabels(value) {
|
|
57
|
+
if (typeof value !== 'string' || value.trim() === '') return {}
|
|
58
|
+
const labels = {}
|
|
59
|
+
for (const entry of value.split(',')) {
|
|
60
|
+
const separator = entry.indexOf('=')
|
|
61
|
+
if (separator <= 0) continue
|
|
62
|
+
labels[entry.slice(0, separator).trim()] = entry.slice(separator + 1).trim()
|
|
63
|
+
}
|
|
64
|
+
return labels
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** @param {unknown} value */
|
|
68
|
+
function portList(value) {
|
|
69
|
+
if (typeof value !== 'string' || value.trim() === '') return []
|
|
70
|
+
return value.split(',').map((port) => port.trim()).filter(Boolean)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** @param {Record<string, any>} raw */
|
|
74
|
+
export function normalizeContainer(raw) {
|
|
75
|
+
const names = textOf(raw.Names).split(',').map((name) => name.trim()).filter(Boolean)
|
|
76
|
+
return {
|
|
77
|
+
id: textOf(raw.ID),
|
|
78
|
+
name: names[0] ?? textOf(raw.Names),
|
|
79
|
+
names,
|
|
80
|
+
image: textOf(raw.Image),
|
|
81
|
+
command: textOf(raw.Command),
|
|
82
|
+
createdAt: textOf(raw.CreatedAt),
|
|
83
|
+
runningFor: textOf(raw.RunningFor),
|
|
84
|
+
ports: portList(raw.Ports),
|
|
85
|
+
status: textOf(raw.Status),
|
|
86
|
+
state: textOf(raw.State).toLowerCase() || 'unknown',
|
|
87
|
+
size: textOf(raw.Size),
|
|
88
|
+
labels: parseLabels(raw.Labels),
|
|
89
|
+
mounts: textOf(raw.Mounts),
|
|
90
|
+
networks: textOf(raw.Networks),
|
|
91
|
+
raw,
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** @param {Record<string, any>} raw */
|
|
96
|
+
export function normalizeImage(raw) {
|
|
97
|
+
const repository = textOf(raw.Repository)
|
|
98
|
+
const tag = textOf(raw.Tag)
|
|
99
|
+
return {
|
|
100
|
+
id: textOf(raw.ID),
|
|
101
|
+
repository,
|
|
102
|
+
tag,
|
|
103
|
+
name: repository === '<none>' && tag === '<none>' ? '<none>' : `${repository}${tag ? `:${tag}` : ''}`,
|
|
104
|
+
createdAt: textOf(raw.CreatedAt),
|
|
105
|
+
createdSince: textOf(raw.CreatedSince),
|
|
106
|
+
size: textOf(raw.Size),
|
|
107
|
+
sharedSize: textOf(raw.SharedSize),
|
|
108
|
+
uniqueSize: textOf(raw.UniqueSize),
|
|
109
|
+
containers: textOf(raw.Containers),
|
|
110
|
+
raw,
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** @param {Record<string, any>} raw */
|
|
115
|
+
export function normalizeVolume(raw) {
|
|
116
|
+
return {
|
|
117
|
+
name: textOf(raw.Name),
|
|
118
|
+
driver: textOf(raw.Driver),
|
|
119
|
+
scope: textOf(raw.Scope),
|
|
120
|
+
mountpoint: textOf(raw.Mountpoint),
|
|
121
|
+
labels: parseLabels(raw.Labels),
|
|
122
|
+
raw,
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** @param {Record<string, any>} raw */
|
|
127
|
+
export function normalizeNetwork(raw) {
|
|
128
|
+
return {
|
|
129
|
+
id: textOf(raw.ID),
|
|
130
|
+
name: textOf(raw.Name),
|
|
131
|
+
driver: textOf(raw.Driver),
|
|
132
|
+
scope: textOf(raw.Scope),
|
|
133
|
+
ipv4: textOf(raw.IPv4),
|
|
134
|
+
ipv6: textOf(raw.IPv6),
|
|
135
|
+
internal: textOf(raw.Internal),
|
|
136
|
+
createdAt: textOf(raw.CreatedAt),
|
|
137
|
+
labels: parseLabels(raw.Labels),
|
|
138
|
+
raw,
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** @param {Record<string, any>} raw */
|
|
143
|
+
export function normalizeCompose(raw) {
|
|
144
|
+
return {
|
|
145
|
+
name: textOf(raw.Name ?? raw.name),
|
|
146
|
+
status: textOf(raw.Status ?? raw.status),
|
|
147
|
+
configFiles: textOf(raw.ConfigFiles ?? raw.configFiles),
|
|
148
|
+
raw,
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* @param {string} text
|
|
154
|
+
* @returns {{items: any[], format: 'json'|'text'}}
|
|
155
|
+
*/
|
|
156
|
+
export function parseComposeList(text) {
|
|
157
|
+
try {
|
|
158
|
+
const value = JSON.parse(text.trim())
|
|
159
|
+
if (Array.isArray(value)) return { items: value.map(normalizeCompose), format: 'json' }
|
|
160
|
+
if (value !== null && typeof value === 'object') return { items: [normalizeCompose(value)], format: 'json' }
|
|
161
|
+
} catch {
|
|
162
|
+
// Older Compose releases have no --format json; fall through to the table.
|
|
163
|
+
}
|
|
164
|
+
const lines = text.split(/\r?\n/u).map((line) => line.trim()).filter(Boolean)
|
|
165
|
+
if (lines.length === 0) return { items: [], format: 'text' }
|
|
166
|
+
const header = lines[0]
|
|
167
|
+
if (!/^NAME\b/iu.test(header)) return { items: [], format: 'text' }
|
|
168
|
+
const items = []
|
|
169
|
+
for (const line of lines.slice(1)) {
|
|
170
|
+
const columns = line.split(/\s{2,}/u)
|
|
171
|
+
if (columns.length < 2) continue
|
|
172
|
+
items.push(normalizeCompose({ Name: columns[0], Status: columns[1], ConfigFiles: columns.slice(2).join(' ') }))
|
|
173
|
+
}
|
|
174
|
+
return { items, format: 'text' }
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* @param {string} text
|
|
179
|
+
* @returns {{contexts: Array<{name:string,description:string,endpoint:string,current:boolean,error:string}>}}
|
|
180
|
+
*/
|
|
181
|
+
export function parseContexts(text) {
|
|
182
|
+
const rows = parseNdjson(text)
|
|
183
|
+
return {
|
|
184
|
+
contexts: rows
|
|
185
|
+
.filter((row) => row !== null && typeof row === 'object')
|
|
186
|
+
.map((row) => ({
|
|
187
|
+
name: textOf(row.Name),
|
|
188
|
+
description: textOf(row.Description),
|
|
189
|
+
endpoint: textOf(row.DockerEndpoint),
|
|
190
|
+
current: row.Current === true,
|
|
191
|
+
error: textOf(row.Error),
|
|
192
|
+
}))
|
|
193
|
+
.filter((row) => row.name !== ''),
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** @param {Record<string, any>} raw */
|
|
198
|
+
export function mapDockerVersion(raw) {
|
|
199
|
+
const client = raw.Client !== null && typeof raw.Client === 'object' ? raw.Client : null
|
|
200
|
+
const server = raw.Server !== null && typeof raw.Server === 'object' ? raw.Server : null
|
|
201
|
+
return {
|
|
202
|
+
client:
|
|
203
|
+
client === null
|
|
204
|
+
? null
|
|
205
|
+
: {
|
|
206
|
+
version: textOf(client.Version),
|
|
207
|
+
apiVersion: textOf(client.ApiVersion),
|
|
208
|
+
context: textOf(client.Context),
|
|
209
|
+
os: textOf(client.Os),
|
|
210
|
+
arch: textOf(client.Arch),
|
|
211
|
+
gitCommit: textOf(client.GitCommit),
|
|
212
|
+
goVersion: textOf(client.GoVersion),
|
|
213
|
+
},
|
|
214
|
+
server:
|
|
215
|
+
server === null
|
|
216
|
+
? null
|
|
217
|
+
: {
|
|
218
|
+
version: textOf(server.Version),
|
|
219
|
+
apiVersion: textOf(server.ApiVersion),
|
|
220
|
+
os: textOf(server.Os),
|
|
221
|
+
arch: textOf(server.Arch),
|
|
222
|
+
kernelVersion: textOf(server.KernelVersion),
|
|
223
|
+
gitCommit: textOf(server.GitCommit),
|
|
224
|
+
goVersion: textOf(server.GoVersion),
|
|
225
|
+
},
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/** @param {Record<string, any>} raw */
|
|
230
|
+
export function mapDockerInfo(raw) {
|
|
231
|
+
const client = raw.ClientInfo !== null && typeof raw.ClientInfo === 'object' ? raw.ClientInfo : null
|
|
232
|
+
return {
|
|
233
|
+
server: {
|
|
234
|
+
name: textOf(raw.Name),
|
|
235
|
+
version: textOf(raw.ServerVersion),
|
|
236
|
+
osType: textOf(raw.OSType),
|
|
237
|
+
operatingSystem: textOf(raw.OperatingSystem),
|
|
238
|
+
architecture: textOf(raw.Architecture),
|
|
239
|
+
kernelVersion: textOf(raw.KernelVersion),
|
|
240
|
+
driver: textOf(raw.Driver),
|
|
241
|
+
dockerRootDir: textOf(raw.DockerRootDir),
|
|
242
|
+
ncpu: Number(raw.NCPU) || 0,
|
|
243
|
+
memTotal: Number(raw.MemTotal) || 0,
|
|
244
|
+
containers: Number(raw.Containers) || 0,
|
|
245
|
+
containersRunning: Number(raw.ContainersRunning) || 0,
|
|
246
|
+
containersPaused: Number(raw.ContainersPaused) || 0,
|
|
247
|
+
containersStopped: Number(raw.ContainersStopped) || 0,
|
|
248
|
+
images: Number(raw.Images) || 0,
|
|
249
|
+
warnings: Array.isArray(raw.Warnings) ? raw.Warnings.map(String) : [],
|
|
250
|
+
},
|
|
251
|
+
client:
|
|
252
|
+
client === null
|
|
253
|
+
? null
|
|
254
|
+
: {
|
|
255
|
+
version: textOf(client.Version),
|
|
256
|
+
apiVersion: textOf(client.DefaultAPIVersion),
|
|
257
|
+
os: textOf(client.Platform?.Name),
|
|
258
|
+
arch: textOf(client.Arch),
|
|
259
|
+
context: textOf(client.Context),
|
|
260
|
+
},
|
|
261
|
+
raw,
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/** @param {string} text */
|
|
266
|
+
export function parseColimaList(text) {
|
|
267
|
+
const rows = parseNdjson(text)
|
|
268
|
+
return rows.map((row) => ({
|
|
269
|
+
name: textOf(row.name),
|
|
270
|
+
status: textOf(row.status),
|
|
271
|
+
arch: textOf(row.arch),
|
|
272
|
+
cpus: Number(row.cpus) || 0,
|
|
273
|
+
memory: Number(row.memory) || 0,
|
|
274
|
+
disk: Number(row.disk) || 0,
|
|
275
|
+
runtime: textOf(row.runtime),
|
|
276
|
+
raw: row,
|
|
277
|
+
}))
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/** @param {string} text */
|
|
281
|
+
export function parseColimaStatus(text) {
|
|
282
|
+
const value = parseJsonLoose(text)
|
|
283
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) return null
|
|
284
|
+
return {
|
|
285
|
+
displayName: textOf(value.display_name),
|
|
286
|
+
driver: textOf(value.driver),
|
|
287
|
+
arch: textOf(value.arch),
|
|
288
|
+
runtime: textOf(value.runtime),
|
|
289
|
+
mountType: textOf(value.mount_type),
|
|
290
|
+
ipAddress: textOf(value.ip_address),
|
|
291
|
+
dockerSocket: textOf(value.docker_socket),
|
|
292
|
+
kubernetes: value.kubernetes === true,
|
|
293
|
+
cpus: Number(value.cpu) || 0,
|
|
294
|
+
memory: Number(value.memory) || 0,
|
|
295
|
+
disk: Number(value.disk) || 0,
|
|
296
|
+
raw: value,
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Classify a Docker endpoint without deciding whether a local app exists.
|
|
302
|
+
* @param {string | null | undefined} endpoint
|
|
303
|
+
* @returns {{kind:'colima'|'orbstack'|'local-socket'|'remote'|'unknown', profile?:string}}
|
|
304
|
+
*/
|
|
305
|
+
export function classifyEndpoint(endpoint) {
|
|
306
|
+
const value = textOf(endpoint)
|
|
307
|
+
if (value === '') return { kind: 'unknown' }
|
|
308
|
+
const colima = /\.colima\/([^/]+)\/docker\.sock/iu.exec(value)
|
|
309
|
+
if (colima !== null) return { kind: 'colima', profile: colima[1] }
|
|
310
|
+
if (/colima/iu.test(value)) return { kind: 'colima', profile: 'default' }
|
|
311
|
+
if (/orbstack/iu.test(value)) return { kind: 'orbstack' }
|
|
312
|
+
if (/^(?:tcp|ssh|http|https):\/\//iu.test(value)) return { kind: 'remote' }
|
|
313
|
+
if (/^(?:unix|npipe):/iu.test(value)) return { kind: 'local-socket' }
|
|
314
|
+
return { kind: 'unknown' }
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/** @param {string} text */
|
|
318
|
+
export function parseSystemDf(text) {
|
|
319
|
+
return parseNdjson(text).map((row) => ({
|
|
320
|
+
type: textOf(row.Type),
|
|
321
|
+
totalCount: Number(row.TotalCount) || 0,
|
|
322
|
+
activeCount: Number(row.ActiveCount) || 0,
|
|
323
|
+
size: textOf(row.Size),
|
|
324
|
+
reclaimable: textOf(row.Reclaimable),
|
|
325
|
+
raw: row,
|
|
326
|
+
}))
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/** @param {string} text */
|
|
330
|
+
export function parseAppleJson(text) {
|
|
331
|
+
const value = JSON.parse(text.trim())
|
|
332
|
+
return value
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/** @param {Record<string, any>} raw */
|
|
336
|
+
function applePort(value) {
|
|
337
|
+
if (typeof value === 'string') return value
|
|
338
|
+
if (value === null || typeof value !== 'object') return ''
|
|
339
|
+
const host = textOf(value.hostAddress ?? value.address ?? value.host ?? '0.0.0.0')
|
|
340
|
+
const hostPort = textOf(value.hostPort ?? value.host_port ?? '')
|
|
341
|
+
const containerPort = textOf(value.containerPort ?? value.container_port ?? value.targetPort ?? '')
|
|
342
|
+
const protocol = textOf(value.protocol ?? 'tcp')
|
|
343
|
+
if (containerPort === '') return ''
|
|
344
|
+
return `${host}${hostPort === '' ? '' : `:${hostPort}`}->${containerPort}/${protocol}`
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/** @param {Record<string, any>} raw */
|
|
348
|
+
export function normalizeAppleContainer(raw) {
|
|
349
|
+
const config = raw.configuration !== null && typeof raw.configuration === 'object' ? raw.configuration : {}
|
|
350
|
+
const status = raw.status !== null && typeof raw.status === 'object' ? raw.status : {}
|
|
351
|
+
const init = config.initProcess !== null && typeof config.initProcess === 'object' ? config.initProcess : {}
|
|
352
|
+
const args = Array.isArray(init.arguments) ? init.arguments.map(String) : []
|
|
353
|
+
const networks = Array.isArray(config.networks)
|
|
354
|
+
? config.networks.map((entry) => textOf(entry?.network ?? entry?.name ?? entry)).filter(Boolean)
|
|
355
|
+
: []
|
|
356
|
+
const mounts = Array.isArray(config.mounts) ? config.mounts : []
|
|
357
|
+
return {
|
|
358
|
+
id: textOf(config.id ?? raw.id),
|
|
359
|
+
name: textOf(config.id ?? raw.id),
|
|
360
|
+
names: [textOf(config.id ?? raw.id)].filter(Boolean),
|
|
361
|
+
image: textOf(config.image?.reference ?? config.image?.descriptor?.digest),
|
|
362
|
+
command: [textOf(init.executable), ...args].filter(Boolean).join(' '),
|
|
363
|
+
createdAt: textOf(config.creationDate),
|
|
364
|
+
runningFor: textOf(status.startedDate),
|
|
365
|
+
ports: (Array.isArray(config.publishedPorts) ? config.publishedPorts : []).map(applePort).filter(Boolean),
|
|
366
|
+
status: textOf(status.state),
|
|
367
|
+
state: textOf(status.state).toLowerCase() || 'unknown',
|
|
368
|
+
size: '',
|
|
369
|
+
labels: config.labels !== null && typeof config.labels === 'object' ? config.labels : {},
|
|
370
|
+
mounts,
|
|
371
|
+
networks: networks.join(','),
|
|
372
|
+
raw,
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/** @param {Record<string, any>} raw */
|
|
377
|
+
export function normalizeAppleImage(raw) {
|
|
378
|
+
const config = raw.configuration !== null && typeof raw.configuration === 'object' ? raw.configuration : {}
|
|
379
|
+
const reference = textOf(config.name ?? raw.id)
|
|
380
|
+
const lastSlash = reference.lastIndexOf('/')
|
|
381
|
+
const lastColon = reference.lastIndexOf(':')
|
|
382
|
+
const hasTag = lastColon > lastSlash
|
|
383
|
+
const repository = hasTag ? reference.slice(0, lastColon) : reference
|
|
384
|
+
const tag = hasTag ? reference.slice(lastColon + 1) : ''
|
|
385
|
+
const variants = Array.isArray(raw.variants) ? raw.variants : []
|
|
386
|
+
const size = variants.reduce((sum, variant) => sum + (Number(variant?.size) || 0), 0)
|
|
387
|
+
const architectures = []
|
|
388
|
+
for (const variant of variants) {
|
|
389
|
+
const platform = variant?.platform ?? {
|
|
390
|
+
os: variant?.config?.os,
|
|
391
|
+
architecture: variant?.config?.architecture,
|
|
392
|
+
variant: variant?.config?.variant,
|
|
393
|
+
}
|
|
394
|
+
const parts = [platform?.os, platform?.architecture, platform?.variant].filter(Boolean)
|
|
395
|
+
if (parts.length >= 2) architectures.push(parts.join('/'))
|
|
396
|
+
}
|
|
397
|
+
return {
|
|
398
|
+
id: textOf(raw.id ?? config.descriptor?.digest),
|
|
399
|
+
repository,
|
|
400
|
+
tag,
|
|
401
|
+
name: reference,
|
|
402
|
+
createdAt: textOf(config.creationDate),
|
|
403
|
+
createdSince: textOf(config.creationDate),
|
|
404
|
+
size: size > 0 ? formatBytes(size) : '',
|
|
405
|
+
sharedSize: '',
|
|
406
|
+
uniqueSize: '',
|
|
407
|
+
containers: '',
|
|
408
|
+
architecture: [...new Set(architectures)].join(', '),
|
|
409
|
+
raw,
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/** @param {Record<string, any>} raw */
|
|
414
|
+
export function normalizeAppleVolume(raw) {
|
|
415
|
+
const config = raw.configuration !== null && typeof raw.configuration === 'object' ? raw.configuration : {}
|
|
416
|
+
return {
|
|
417
|
+
name: textOf(config.name ?? raw.id),
|
|
418
|
+
driver: textOf(config.driver),
|
|
419
|
+
scope: 'local',
|
|
420
|
+
mountpoint: textOf(config.source),
|
|
421
|
+
labels: config.labels !== null && typeof config.labels === 'object' ? config.labels : {},
|
|
422
|
+
createdAt: textOf(config.creationDate),
|
|
423
|
+
size: Number(config.sizeInBytes) || 0,
|
|
424
|
+
raw,
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/** @param {Record<string, any>} raw */
|
|
429
|
+
export function normalizeAppleNetwork(raw) {
|
|
430
|
+
const config = raw.configuration !== null && typeof raw.configuration === 'object' ? raw.configuration : {}
|
|
431
|
+
const status = raw.status !== null && typeof raw.status === 'object' ? raw.status : {}
|
|
432
|
+
return {
|
|
433
|
+
id: textOf(config.name ?? raw.id),
|
|
434
|
+
name: textOf(config.name ?? raw.id),
|
|
435
|
+
driver: textOf(config.plugin ?? config.mode),
|
|
436
|
+
scope: 'local',
|
|
437
|
+
ipv4: textOf(status.ipv4Subnet),
|
|
438
|
+
ipv6: textOf(status.ipv6Subnet),
|
|
439
|
+
internal: textOf(config.mode),
|
|
440
|
+
createdAt: textOf(config.creationDate),
|
|
441
|
+
labels: config.labels !== null && typeof config.labels === 'object' ? config.labels : {},
|
|
442
|
+
raw,
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/** @param {Record<string, any>} raw */
|
|
447
|
+
export function parseAppleSystemStatus(raw) {
|
|
448
|
+
return {
|
|
449
|
+
status: textOf(raw?.status),
|
|
450
|
+
apiServerVersion: textOf(raw?.apiServerVersion),
|
|
451
|
+
apiServerBuild: textOf(raw?.apiServerBuild),
|
|
452
|
+
apiServerCommit: textOf(raw?.apiServerCommit),
|
|
453
|
+
appRoot: textOf(raw?.appRoot),
|
|
454
|
+
installRoot: textOf(raw?.installRoot),
|
|
455
|
+
raw,
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/** @param {any[]} raw */
|
|
460
|
+
export function parseAppleSystemVersion(raw) {
|
|
461
|
+
const rows = Array.isArray(raw) ? raw : raw === null || typeof raw !== 'object' ? [] : [raw]
|
|
462
|
+
const component = (name) => rows.find((row) => textOf(row?.appName) === name)
|
|
463
|
+
return {
|
|
464
|
+
containerVersion: textOf(component('container')?.version),
|
|
465
|
+
apiServerVersion: textOf(component('container-apiserver')?.version),
|
|
466
|
+
components: rows,
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/** @param {Record<string, any>} raw */
|
|
471
|
+
export function parseAppleDf(raw) {
|
|
472
|
+
const item = (key, type) => {
|
|
473
|
+
const value = raw?.[key] !== null && typeof raw?.[key] === 'object' ? raw[key] : {}
|
|
474
|
+
return {
|
|
475
|
+
type,
|
|
476
|
+
totalCount: Number(value.total) || 0,
|
|
477
|
+
activeCount: Number(value.active) || 0,
|
|
478
|
+
size: formatBytes(Number(value.sizeInBytes) || 0),
|
|
479
|
+
reclaimable: formatBytes(Number(value.reclaimable) || 0),
|
|
480
|
+
raw: value,
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
return [item('images', 'Images'), item('containers', 'Containers'), item('volumes', 'Local Volumes')]
|
|
484
|
+
}
|