docker-client-mx 1.35.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 +233 -0
- package/bin/docker-client-mx.js +42 -0
- package/build/icons/icon-1024.png +0 -0
- package/build/icons/icon-128.png +0 -0
- package/build/icons/icon-16.png +0 -0
- package/build/icons/icon-256.png +0 -0
- package/build/icons/icon-32.png +0 -0
- package/build/icons/icon-48.png +0 -0
- package/build/icons/icon-512.png +0 -0
- package/build/icons/icon-64.png +0 -0
- package/build/icons/icon.ico +0 -0
- package/build/icons/icon.iconset/icon_128x128.png +0 -0
- package/build/icons/icon.iconset/icon_128x128@2x.png +0 -0
- package/build/icons/icon.iconset/icon_16x16.png +0 -0
- package/build/icons/icon.iconset/icon_16x16@2x.png +0 -0
- package/build/icons/icon.iconset/icon_256x256.png +0 -0
- package/build/icons/icon.iconset/icon_256x256@2x.png +0 -0
- package/build/icons/icon.iconset/icon_32x32.png +0 -0
- package/build/icons/icon.iconset/icon_32x32@2x.png +0 -0
- package/build/icons/icon.iconset/icon_512x512.png +0 -0
- package/build/icons/icon.iconset/icon_512x512@2x.png +0 -0
- package/build/icons/icon.png +0 -0
- package/dist/docker-desktop-mx/browser/chunk-64WWLMUP.js +39 -0
- package/dist/docker-desktop-mx/browser/chunk-N4VBBC4W.js +1 -0
- package/dist/docker-desktop-mx/browser/favicon.ico +0 -0
- package/dist/docker-desktop-mx/browser/index.html +14 -0
- package/dist/docker-desktop-mx/browser/main-CTIRTSOI.js +5 -0
- package/dist/docker-desktop-mx/browser/styles-JS3HDDI4.css +1 -0
- package/dist-electron/api.js +10 -0
- package/dist-electron/api.js.map +1 -0
- package/dist-electron/assistant-key.js +70 -0
- package/dist-electron/assistant-key.js.map +1 -0
- package/dist-electron/assistant-models.js +19 -0
- package/dist-electron/assistant-models.js.map +1 -0
- package/dist-electron/assistant-tools.js +335 -0
- package/dist-electron/assistant-tools.js.map +1 -0
- package/dist-electron/assistant.js +302 -0
- package/dist-electron/assistant.js.map +1 -0
- package/dist-electron/attention.js +75 -0
- package/dist-electron/attention.js.map +1 -0
- package/dist-electron/channels.js +113 -0
- package/dist-electron/channels.js.map +1 -0
- package/dist-electron/compose-files.js +104 -0
- package/dist-electron/compose-files.js.map +1 -0
- package/dist-electron/compose.js +271 -0
- package/dist-electron/compose.js.map +1 -0
- package/dist-electron/container-events.js +188 -0
- package/dist-electron/container-events.js.map +1 -0
- package/dist-electron/containers.js +165 -0
- package/dist-electron/containers.js.map +1 -0
- package/dist-electron/context-menu.js +56 -0
- package/dist-electron/context-menu.js.map +1 -0
- package/dist-electron/contexts.js +346 -0
- package/dist-electron/contexts.js.map +1 -0
- package/dist-electron/crash.js +129 -0
- package/dist-electron/crash.js.map +1 -0
- package/dist-electron/diagnosis.js +338 -0
- package/dist-electron/diagnosis.js.map +1 -0
- package/dist-electron/docker-client.js +284 -0
- package/dist-electron/docker-client.js.map +1 -0
- package/dist-electron/engine.js +145 -0
- package/dist-electron/engine.js.map +1 -0
- package/dist-electron/event-store.js +146 -0
- package/dist-electron/event-store.js.map +1 -0
- package/dist-electron/events.js +132 -0
- package/dist-electron/events.js.map +1 -0
- package/dist-electron/exec.js +138 -0
- package/dist-electron/exec.js.map +1 -0
- package/dist-electron/files.js +407 -0
- package/dist-electron/files.js.map +1 -0
- package/dist-electron/health.js +92 -0
- package/dist-electron/health.js.map +1 -0
- package/dist-electron/host-overview.js +50 -0
- package/dist-electron/host-overview.js.map +1 -0
- package/dist-electron/host-shell.js +196 -0
- package/dist-electron/host-shell.js.map +1 -0
- package/dist-electron/i18n.js +58 -0
- package/dist-electron/i18n.js.map +1 -0
- package/dist-electron/image-history.js +87 -0
- package/dist-electron/image-history.js.map +1 -0
- package/dist-electron/images.js +141 -0
- package/dist-electron/images.js.map +1 -0
- package/dist-electron/ipc.js +783 -0
- package/dist-electron/ipc.js.map +1 -0
- package/dist-electron/links.js +46 -0
- package/dist-electron/links.js.map +1 -0
- package/dist-electron/logs.js +153 -0
- package/dist-electron/logs.js.map +1 -0
- package/dist-electron/main.js +494 -0
- package/dist-electron/main.js.map +1 -0
- package/dist-electron/merged-logs.js +127 -0
- package/dist-electron/merged-logs.js.map +1 -0
- package/dist-electron/navigation.js +37 -0
- package/dist-electron/navigation.js.map +1 -0
- package/dist-electron/ndjson.js +30 -0
- package/dist-electron/ndjson.js.map +1 -0
- package/dist-electron/networks.js +507 -0
- package/dist-electron/networks.js.map +1 -0
- package/dist-electron/port-conflicts.js +106 -0
- package/dist-electron/port-conflicts.js.map +1 -0
- package/dist-electron/preload.js +257 -0
- package/dist-electron/preload.js.map +1 -0
- package/dist-electron/pull.js +220 -0
- package/dist-electron/pull.js.map +1 -0
- package/dist-electron/redact.js +129 -0
- package/dist-electron/redact.js.map +1 -0
- package/dist-electron/registry-auth.js +150 -0
- package/dist-electron/registry-auth.js.map +1 -0
- package/dist-electron/renew.js +162 -0
- package/dist-electron/renew.js.map +1 -0
- package/dist-electron/restart-policy.js +58 -0
- package/dist-electron/restart-policy.js.map +1 -0
- package/dist-electron/run-command.js +146 -0
- package/dist-electron/run-command.js.map +1 -0
- package/dist-electron/run.js +151 -0
- package/dist-electron/run.js.map +1 -0
- package/dist-electron/self-update.js +163 -0
- package/dist-electron/self-update.js.map +1 -0
- package/dist-electron/servers.js +108 -0
- package/dist-electron/servers.js.map +1 -0
- package/dist-electron/settings.js +146 -0
- package/dist-electron/settings.js.map +1 -0
- package/dist-electron/ssh.js +574 -0
- package/dist-electron/ssh.js.map +1 -0
- package/dist-electron/stats.js +209 -0
- package/dist-electron/stats.js.map +1 -0
- package/dist-electron/streams.js +63 -0
- package/dist-electron/streams.js.map +1 -0
- package/dist-electron/system.js +123 -0
- package/dist-electron/system.js.map +1 -0
- package/dist-electron/terminal-transcript.js +55 -0
- package/dist-electron/terminal-transcript.js.map +1 -0
- package/dist-electron/updates.js +136 -0
- package/dist-electron/updates.js.map +1 -0
- package/dist-electron/volume-backup.js +203 -0
- package/dist-electron/volume-backup.js.map +1 -0
- package/dist-electron/volume-files.js +124 -0
- package/dist-electron/volume-files.js.map +1 -0
- package/dist-electron/volumes.js +253 -0
- package/dist-electron/volumes.js.map +1 -0
- package/dist-electron/wiring-watch.js +93 -0
- package/dist-electron/wiring-watch.js.map +1 -0
- package/dist-electron/wiring.js +71 -0
- package/dist-electron/wiring.js.map +1 -0
- package/dist-electron/wsl.js +167 -0
- package/dist-electron/wsl.js.map +1 -0
- package/package.json +125 -0
|
@@ -0,0 +1,783 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.startCrashWatch = startCrashWatch;
|
|
37
|
+
exports.flushEventHistory = flushEventHistory;
|
|
38
|
+
exports.registerIpcHandlers = registerIpcHandlers;
|
|
39
|
+
exports.attachWindow = attachWindow;
|
|
40
|
+
const electron_1 = require("electron");
|
|
41
|
+
const node_fs_1 = require("node:fs");
|
|
42
|
+
const path = __importStar(require("node:path"));
|
|
43
|
+
const zod_1 = require("zod");
|
|
44
|
+
const channels_1 = require("./channels");
|
|
45
|
+
const compose_1 = require("./compose");
|
|
46
|
+
const compose_files_1 = require("./compose-files");
|
|
47
|
+
const containers_1 = require("./containers");
|
|
48
|
+
const port_conflicts_1 = require("./port-conflicts");
|
|
49
|
+
const assistant_1 = require("./assistant");
|
|
50
|
+
const assistant_key_1 = require("./assistant-key");
|
|
51
|
+
const assistant_models_1 = require("./assistant-models");
|
|
52
|
+
const contexts_1 = require("./contexts");
|
|
53
|
+
const diagnosis_1 = require("./diagnosis");
|
|
54
|
+
const docker_client_1 = require("./docker-client");
|
|
55
|
+
const engine_1 = require("./engine");
|
|
56
|
+
const health_1 = require("./health");
|
|
57
|
+
const wiring_1 = require("./wiring");
|
|
58
|
+
const attention_1 = require("./attention");
|
|
59
|
+
const wiring_watch_1 = require("./wiring-watch");
|
|
60
|
+
const image_history_1 = require("./image-history");
|
|
61
|
+
const restart_policy_1 = require("./restart-policy");
|
|
62
|
+
const run_command_1 = require("./run-command");
|
|
63
|
+
const exec_1 = require("./exec");
|
|
64
|
+
const host_shell_1 = require("./host-shell");
|
|
65
|
+
const host_overview_1 = require("./host-overview");
|
|
66
|
+
const terminal_transcript_1 = require("./terminal-transcript");
|
|
67
|
+
const crash_1 = require("./crash");
|
|
68
|
+
const container_events_1 = require("./container-events");
|
|
69
|
+
const event_store_1 = require("./event-store");
|
|
70
|
+
const events_1 = require("./events");
|
|
71
|
+
const files_1 = require("./files");
|
|
72
|
+
const volume_files_1 = require("./volume-files");
|
|
73
|
+
const self_update_1 = require("./self-update");
|
|
74
|
+
const settings_1 = require("./settings");
|
|
75
|
+
const servers_1 = require("./servers");
|
|
76
|
+
const i18n_1 = require("./i18n");
|
|
77
|
+
const updates_1 = require("./updates");
|
|
78
|
+
const volume_backup_1 = require("./volume-backup");
|
|
79
|
+
const navigation_1 = require("./navigation");
|
|
80
|
+
const images_1 = require("./images");
|
|
81
|
+
const logs_1 = require("./logs");
|
|
82
|
+
const networks_1 = require("./networks");
|
|
83
|
+
const pull_1 = require("./pull");
|
|
84
|
+
const renew_1 = require("./renew");
|
|
85
|
+
const run_1 = require("./run");
|
|
86
|
+
const volumes_1 = require("./volumes");
|
|
87
|
+
const merged_logs_1 = require("./merged-logs");
|
|
88
|
+
const stats_1 = require("./stats");
|
|
89
|
+
const streams_1 = require("./streams");
|
|
90
|
+
const system_1 = require("./system");
|
|
91
|
+
/**
|
|
92
|
+
* Alles wat de renderer aanlevert wordt hier gecontroleerd voordat het een dockerode-aanroep
|
|
93
|
+
* in gaat. De renderer draait gesandboxed en laadt geen vreemde inhoud, dus dit is vooral een
|
|
94
|
+
* vangnet voor onze eigen fouten — maar het is het enige punt waar de grens ligt.
|
|
95
|
+
*/
|
|
96
|
+
const containerAction = zod_1.z.enum(['start', 'stop', 'restart', 'pause', 'unpause', 'remove']);
|
|
97
|
+
const containerId = zod_1.z.string().min(1).max(128);
|
|
98
|
+
// Het maximum telt alleen bij on-failure; honderd pogingen is al ruim voorbij waar het nog zin heeft.
|
|
99
|
+
const restartPolicy = zod_1.z.object({
|
|
100
|
+
name: zod_1.z.enum(['no', 'always', 'unless-stopped', 'on-failure']),
|
|
101
|
+
maximumRetryCount: zod_1.z.number().int().min(0).max(100),
|
|
102
|
+
});
|
|
103
|
+
const containerIds = zod_1.z.array(containerId).max(1000);
|
|
104
|
+
const streamId = zod_1.z.string().min(1).max(64);
|
|
105
|
+
const logTail = zod_1.z.number().int().min(0).max(10_000);
|
|
106
|
+
// Een referentie mag een registryhost, een pad, een tag en een digest bevatten; ruimer dan
|
|
107
|
+
// dit wordt het niet, en alles wat er niet in past wijst de daemon toch af.
|
|
108
|
+
const imageReference = zod_1.z.string().min(1).max(256).regex(/^[\w./:@-]+$/);
|
|
109
|
+
const truthy = zod_1.z.boolean();
|
|
110
|
+
// Een pad in de container. files.ts maakt het absoluut en net; hier alleen een grens.
|
|
111
|
+
const containerPath = zod_1.z.string().min(1).max(4096);
|
|
112
|
+
const fileKind = zod_1.z.enum(['dir', 'file']);
|
|
113
|
+
// Een sleutel van de Claude API is een regel van een paar honderd tekens; main kijkt de vorm na.
|
|
114
|
+
const apiKey = zod_1.z.string().min(1).max(512);
|
|
115
|
+
// Een vraag aan Claude. Ruim, maar geen heel logbestand: dat haalt Claude zelf op.
|
|
116
|
+
const question = zod_1.z.string().trim().min(1).max(4000);
|
|
117
|
+
// Alleen de schakelaars; `strict` zodat een onbekende sleutel niet stil genegeerd wordt.
|
|
118
|
+
const settingsPatch = zod_1.z
|
|
119
|
+
.object({
|
|
120
|
+
startAtLogin: zod_1.z.boolean().optional(),
|
|
121
|
+
notifyCrash: zod_1.z.boolean().optional(),
|
|
122
|
+
notifyUnhealthy: zod_1.z.boolean().optional(),
|
|
123
|
+
notifyWiring: zod_1.z.boolean().optional(),
|
|
124
|
+
autoUpdate: zod_1.z.boolean().optional(),
|
|
125
|
+
language: zod_1.z.enum(i18n_1.LANGUAGES).optional(),
|
|
126
|
+
assistantModel: zod_1.z.enum(assistant_models_1.ASSISTANT_MODELS).optional(),
|
|
127
|
+
})
|
|
128
|
+
.strict();
|
|
129
|
+
// Lokale paden die de preload uit gesleepte File-objecten haalde. Absoluut, en een handvol.
|
|
130
|
+
const localPaths = zod_1.z.array(zod_1.z.string().min(1).max(4096).refine((value) => path.isAbsolute(value))).max(100);
|
|
131
|
+
// Een distronaam zoals WSL die toestaat. main controleert daarna nog of hij in `wsl -l` staat.
|
|
132
|
+
const wslDistro = zod_1.z.string().min(1).max(64).regex(/^[\w.-]+$/);
|
|
133
|
+
// Volumenamen, netwerk-id's en contextnamen komen uit onze eigen lijst; dit is een grens,
|
|
134
|
+
// geen validatie van wat Docker toelaat.
|
|
135
|
+
const resourceName = zod_1.z.string().min(1).max(256);
|
|
136
|
+
// Een extra naam in een netwerk. Docker laat hier hetzelfde toe als bij een containernaam.
|
|
137
|
+
const networkAlias = zod_1.z
|
|
138
|
+
.string()
|
|
139
|
+
.min(1)
|
|
140
|
+
.max(128)
|
|
141
|
+
.regex(/^[\w][\w.-]*$/, {
|
|
142
|
+
error: () => (0, i18n_1.t)({
|
|
143
|
+
en: 'An alias may only contain letters, digits, dots, hyphens and underscores.',
|
|
144
|
+
nl: 'Een alias mag alleen letters, cijfers, punt, streepje en underscore bevatten.',
|
|
145
|
+
de: 'Ein Alias darf nur Buchstaben, Ziffern, Punkte, Bindestriche und Unterstriche enthalten.',
|
|
146
|
+
fr: 'Un alias ne peut contenir que des lettres, des chiffres, des points, des tirets et des underscores.',
|
|
147
|
+
es: 'Un alias solo puede contener letras, dígitos, puntos, guiones y guiones bajos.',
|
|
148
|
+
}),
|
|
149
|
+
});
|
|
150
|
+
/** Vorm en lengte; of het subnet en de gateway kloppen zegt createNetwork, in de taal van de app. */
|
|
151
|
+
const newNetwork = zod_1.z.object({
|
|
152
|
+
name: resourceName,
|
|
153
|
+
// `bridge`, `overlay`, `macvlan`, of een plugin; de daemon zegt zelf of hij hem kent.
|
|
154
|
+
driver: zod_1.z.string().min(1).max(128).regex(/^[\w./:@-]+$/),
|
|
155
|
+
internal: truthy,
|
|
156
|
+
// Ruim genoeg voor een IPv6-CIDR, dat met 39 tekens plus het achtervoegsel het langst is.
|
|
157
|
+
subnet: zod_1.z.string().min(1).max(64).nullable(),
|
|
158
|
+
gateway: zod_1.z.string().min(1).max(64).nullable(),
|
|
159
|
+
});
|
|
160
|
+
/** Vorm en lengte; of het pad en de server kloppen zegt createVolume, in de taal van de app. */
|
|
161
|
+
const newVolume = zod_1.z.object({
|
|
162
|
+
name: resourceName,
|
|
163
|
+
storage: zod_1.z.discriminatedUnion('kind', [
|
|
164
|
+
// `local`, of een plugin als `rexray/ebs:latest`; de daemon zegt zelf of hij hem kent.
|
|
165
|
+
zod_1.z.object({ kind: zod_1.z.literal('docker'), driver: zod_1.z.string().min(1).max(128).regex(/^[\w./:@-]+$/) }),
|
|
166
|
+
zod_1.z.object({ kind: zod_1.z.literal('folder'), path: zod_1.z.string().min(1).max(1024) }),
|
|
167
|
+
zod_1.z.object({ kind: zod_1.z.literal('nfs'), server: zod_1.z.string().min(1).max(253), path: zod_1.z.string().min(1).max(1024) }),
|
|
168
|
+
]),
|
|
169
|
+
});
|
|
170
|
+
/** Vorm en lengte; of de waarden kloppen zegt createContext, met een melding die iemand begrijpt. */
|
|
171
|
+
const newServer = zod_1.z.object({
|
|
172
|
+
name: zod_1.z.string().min(1).max(64),
|
|
173
|
+
description: zod_1.z.string().max(200).nullable(),
|
|
174
|
+
kind: zod_1.z.enum(['ssh', 'tcp']),
|
|
175
|
+
host: zod_1.z.string().min(1).max(253),
|
|
176
|
+
port: zod_1.z.number().int().min(1).max(65_535).nullable(),
|
|
177
|
+
username: zod_1.z.string().max(64).nullable(),
|
|
178
|
+
});
|
|
179
|
+
const serverTestTarget = zod_1.z.union([zod_1.z.object({ context: resourceName }), zod_1.z.object({ server: newServer })]);
|
|
180
|
+
const hostKeyName = zod_1.z.string().min(1).max(300);
|
|
181
|
+
const hostKeyFingerprint = zod_1.z.string().min(1).max(100);
|
|
182
|
+
const terminalSize = zod_1.z.number().int().min(1).max(1000);
|
|
183
|
+
// Toetsaanslagen en geplakte tekst. Ruim genoeg voor een plak-actie, met een bovengrens zodat
|
|
184
|
+
// één bericht niet ongelimiteerd geheugen vraagt.
|
|
185
|
+
const terminalInput = zod_1.z.string().max(100_000);
|
|
186
|
+
// Wat de opruimdialoog aanvinkt. `strict`: een onbekende sleutel is een renderer die iets
|
|
187
|
+
// anders denkt op te ruimen dan main, en dan liever niets doen.
|
|
188
|
+
const pruneSelection = zod_1.z
|
|
189
|
+
.object({
|
|
190
|
+
containers: zod_1.z.boolean(),
|
|
191
|
+
networks: zod_1.z.boolean(),
|
|
192
|
+
volumes: zod_1.z.boolean(),
|
|
193
|
+
images: zod_1.z.boolean(),
|
|
194
|
+
allImages: zod_1.z.boolean(),
|
|
195
|
+
buildCache: zod_1.z.boolean(),
|
|
196
|
+
})
|
|
197
|
+
.strict();
|
|
198
|
+
/**
|
|
199
|
+
* Het run-formulier. Dit is het enige punt in de app waar de renderer een containerspecificatie
|
|
200
|
+
* aanlevert, dus hier wordt echt gecontroleerd: poortnummers binnen bereik, een doelpad dat een
|
|
201
|
+
* absoluut pad ís, en een variabelenaam zonder `=` — die zou de KEY=value-notatie breken.
|
|
202
|
+
*
|
|
203
|
+
* De meldingen als functie: het schema staat vast, de taal niet.
|
|
204
|
+
*/
|
|
205
|
+
const runOptions = zod_1.z.object({
|
|
206
|
+
image: imageReference,
|
|
207
|
+
name: zod_1.z
|
|
208
|
+
.string()
|
|
209
|
+
.max(128)
|
|
210
|
+
.regex(/^[\w][\w.-]*$/, {
|
|
211
|
+
error: () => (0, i18n_1.t)({
|
|
212
|
+
en: 'A container name may only contain letters, digits, dots, hyphens and underscores.',
|
|
213
|
+
nl: 'Een containernaam mag alleen letters, cijfers, punt, streepje en underscore bevatten.',
|
|
214
|
+
de: 'Ein Containername darf nur Buchstaben, Ziffern, Punkte, Bindestriche und Unterstriche enthalten.',
|
|
215
|
+
fr: 'Un nom de conteneur ne peut contenir que des lettres, des chiffres, des points, des tirets et des underscores.',
|
|
216
|
+
es: 'Un nombre de contenedor solo puede contener letras, dígitos, puntos, guiones y guiones bajos.',
|
|
217
|
+
}),
|
|
218
|
+
})
|
|
219
|
+
.nullable(),
|
|
220
|
+
ports: zod_1.z
|
|
221
|
+
.array(zod_1.z.object({
|
|
222
|
+
hostPort: zod_1.z.number().int().min(1).max(65_535).nullable(),
|
|
223
|
+
containerPort: zod_1.z.number().int().min(1).max(65_535),
|
|
224
|
+
protocol: zod_1.z.enum(['tcp', 'udp']),
|
|
225
|
+
}))
|
|
226
|
+
.max(100),
|
|
227
|
+
volumes: zod_1.z
|
|
228
|
+
.array(zod_1.z.object({
|
|
229
|
+
source: zod_1.z.string().min(1).max(1024),
|
|
230
|
+
target: zod_1.z
|
|
231
|
+
.string()
|
|
232
|
+
.min(1)
|
|
233
|
+
.max(1024)
|
|
234
|
+
.startsWith('/', { error: () => (0, i18n_1.t)({
|
|
235
|
+
en: 'The path in the container must be absolute.',
|
|
236
|
+
nl: 'Het pad in de container moet absoluut zijn.',
|
|
237
|
+
de: 'Der Pfad im Container muss absolut sein.',
|
|
238
|
+
fr: 'Le chemin dans le conteneur doit être absolu.',
|
|
239
|
+
es: 'La ruta en el contenedor debe ser absoluta.',
|
|
240
|
+
}) }),
|
|
241
|
+
readOnly: zod_1.z.boolean(),
|
|
242
|
+
}))
|
|
243
|
+
.max(100),
|
|
244
|
+
env: zod_1.z
|
|
245
|
+
.array(zod_1.z.object({
|
|
246
|
+
key: zod_1.z
|
|
247
|
+
.string()
|
|
248
|
+
.min(1)
|
|
249
|
+
.max(256)
|
|
250
|
+
.refine((key) => !key.includes('='), { error: () => (0, i18n_1.t)({
|
|
251
|
+
en: 'A variable name may not contain =.',
|
|
252
|
+
nl: 'Een variabelenaam mag geen = bevatten.',
|
|
253
|
+
de: 'Ein Variablenname darf kein = enthalten.',
|
|
254
|
+
fr: 'Un nom de variable ne peut pas contenir de =.',
|
|
255
|
+
es: 'Un nombre de variable no puede contener =.',
|
|
256
|
+
}) }),
|
|
257
|
+
value: zod_1.z.string().max(32_768),
|
|
258
|
+
}))
|
|
259
|
+
.max(200),
|
|
260
|
+
network: resourceName.nullable(),
|
|
261
|
+
networkAlias: networkAlias.nullable(),
|
|
262
|
+
restart: zod_1.z.enum(['no', 'unless-stopped']),
|
|
263
|
+
});
|
|
264
|
+
/**
|
|
265
|
+
* De containers die meedoen in de gebundelde logweergave. Begrensd zoals de andere lijsten:
|
|
266
|
+
* de renderer stuurt wat er draait, en dat mag niet ongelimiteerd zijn.
|
|
267
|
+
*/
|
|
268
|
+
const logSources = zod_1.z
|
|
269
|
+
.array(zod_1.z.object({
|
|
270
|
+
containerId: zod_1.z.string().min(1).max(128),
|
|
271
|
+
containerName: zod_1.z.string().max(256),
|
|
272
|
+
composeProject: zod_1.z.string().max(256).nullable(),
|
|
273
|
+
composeService: zod_1.z.string().max(256).nullable(),
|
|
274
|
+
}))
|
|
275
|
+
.max(200);
|
|
276
|
+
const windows = new Map();
|
|
277
|
+
/**
|
|
278
|
+
* De open shells, op het id dat de renderer kreeg. Het register hieronder sluit ze als het
|
|
279
|
+
* venster verdwijnt; deze map is er alleen om bij een lopende sessie te kunnen om naar te
|
|
280
|
+
* schrijven en hem te laten schalen.
|
|
281
|
+
*/
|
|
282
|
+
const execSessions = new Map();
|
|
283
|
+
/**
|
|
284
|
+
* Wat de host-shell van elk venster liet zien, voor Claude in de tab ernaast. Per venster en
|
|
285
|
+
* niet per stream: bij opnieuw openen begint een nieuw transcript, en een venster heeft er
|
|
286
|
+
* hooguit één.
|
|
287
|
+
*/
|
|
288
|
+
const hostTranscripts = new Map();
|
|
289
|
+
/**
|
|
290
|
+
* De events-stream voor crashmeldingen. Eén voor de hele app en niet per venster: een melding
|
|
291
|
+
* hoort ook te komen als er geen venster open is en de app in het systeemvak staat.
|
|
292
|
+
*/
|
|
293
|
+
let crashWatch = null;
|
|
294
|
+
/** Een afzender die niets verstuurt; de crashwatcher heeft alleen de ruwe events nodig. */
|
|
295
|
+
const NO_WINDOW = { send: () => undefined, isDestroyed: () => false };
|
|
296
|
+
/**
|
|
297
|
+
* Begint met het volgen van crashes op de huidige verbinding. Na een contextwissel start
|
|
298
|
+
* `useContext` hem opnieuw, met een verse detector: wat de vorige daemon aan stops zag, zegt
|
|
299
|
+
* niets over de nieuwe.
|
|
300
|
+
*/
|
|
301
|
+
function startCrashWatch(onCrash, onWiring = () => undefined) {
|
|
302
|
+
crashWatch?.watcher.stop();
|
|
303
|
+
crashWatch?.wiring.stop();
|
|
304
|
+
// Wat er van de vorige context nog weg moest, eerst; die begint zo meteen niet meer bij.
|
|
305
|
+
events().flush();
|
|
306
|
+
const context = activeContext;
|
|
307
|
+
const detector = new crash_1.CrashDetector();
|
|
308
|
+
// Verder waar het vorige keer ophield, ook als de app intussen herstart is.
|
|
309
|
+
const current = events().load(context);
|
|
310
|
+
const record = (raw) => {
|
|
311
|
+
if (current.record(raw)) {
|
|
312
|
+
events().changed(context, current);
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
history = current;
|
|
316
|
+
// Een container die zijn netwerk of poorten kwijt is: nakijken na elk event dat het kan veroorzaken.
|
|
317
|
+
const wiring = new wiring_watch_1.WiringWatch({ check: () => readAttention(), notify: onWiring });
|
|
318
|
+
const watcher = new events_1.EventsWatcher(NO_WINDOW, undefined, (raw) => {
|
|
319
|
+
record(raw);
|
|
320
|
+
if (wiring_watch_1.WiringWatch.relevant(raw)) {
|
|
321
|
+
wiring.poke();
|
|
322
|
+
}
|
|
323
|
+
const notice = detector.observe(raw);
|
|
324
|
+
if (notice !== null) {
|
|
325
|
+
onCrash(notice);
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
crashWatch = { watcher, wiring, onCrash, onWiring };
|
|
329
|
+
watcher.start();
|
|
330
|
+
// Ook wat er bij het opstarten al mis is; traefik was al stuk toen de app startte.
|
|
331
|
+
wiring.poke();
|
|
332
|
+
// Wat de daemon nog weet, zodat de tab Gebeurtenissen niet leeg begint. Lukt het niet, dan
|
|
333
|
+
// begint hij leeg; dat is geen reden voor een melding.
|
|
334
|
+
(0, container_events_1.readDaemonEvents)()
|
|
335
|
+
.then((daemonEvents) => daemonEvents.forEach(record))
|
|
336
|
+
.catch(() => undefined);
|
|
337
|
+
}
|
|
338
|
+
/** De lijst van fase 40, voor het dashboard en voor de meldingen van fase 43. */
|
|
339
|
+
async function readAttention() {
|
|
340
|
+
return (0, attention_1.buildAttention)({
|
|
341
|
+
containers: await (0, containers_1.listContainers)(),
|
|
342
|
+
eventsOf: (name) => history.eventsOf(name),
|
|
343
|
+
wiringOf: (id) => (0, wiring_1.readContainerWiring)(id),
|
|
344
|
+
now: Date.now(),
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
/** Bij het afsluiten: wat nog op schijf moest, nu. */
|
|
348
|
+
function flushEventHistory() {
|
|
349
|
+
eventStore?.flush();
|
|
350
|
+
}
|
|
351
|
+
let eventStore = null;
|
|
352
|
+
/** Pas bij het eerste gebruik: de gebruikersmap staat pas vast als main.ts hem gezet heeft. */
|
|
353
|
+
function events() {
|
|
354
|
+
const file = () => path.join(electron_1.app.getPath('userData'), 'events.json');
|
|
355
|
+
eventStore ??= new event_store_1.EventStore({
|
|
356
|
+
read: () => {
|
|
357
|
+
try {
|
|
358
|
+
return (0, node_fs_1.readFileSync)(file(), 'utf8');
|
|
359
|
+
}
|
|
360
|
+
catch {
|
|
361
|
+
return null;
|
|
362
|
+
}
|
|
363
|
+
},
|
|
364
|
+
write: (text) => (0, node_fs_1.writeFileSync)(file(), text),
|
|
365
|
+
now: () => Date.now(),
|
|
366
|
+
});
|
|
367
|
+
return eventStore;
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Wat er met containers gebeurd is, per naam. Hoort bij de crashwatcher: die volgt de stream al,
|
|
371
|
+
* app-breed en ook zonder venster, en begint bij een contextwissel opnieuw — net als dit.
|
|
372
|
+
*/
|
|
373
|
+
let history = new container_events_1.ContainerHistory();
|
|
374
|
+
/**
|
|
375
|
+
* Een mislukte compose-opdracht met, bij een bezette poort, wie hem heeft. Hier en niet in
|
|
376
|
+
* compose.ts, want `containers.ts` leunt al op compose.ts en de lijst moet uit containers.ts komen.
|
|
377
|
+
*/
|
|
378
|
+
async function withPortHolder(result) {
|
|
379
|
+
if (result.ok) {
|
|
380
|
+
return result;
|
|
381
|
+
}
|
|
382
|
+
const lead = () => (0, i18n_1.t)({
|
|
383
|
+
en: 'Compose could not start a service: one of its ports is taken.',
|
|
384
|
+
nl: 'Compose kon een service niet starten: een van de poorten is bezet.',
|
|
385
|
+
de: 'Compose konnte einen Dienst nicht starten: einer der Ports ist belegt.',
|
|
386
|
+
fr: 'Compose n’a pas pu démarrer un service : l’un des ports est pris.',
|
|
387
|
+
es: 'Compose no pudo iniciar un servicio: uno de los puertos está ocupado.',
|
|
388
|
+
});
|
|
389
|
+
return { ok: false, problem: await (0, port_conflicts_1.explainPortConflict)(result.problem, containers_1.listContainers, { lead }) };
|
|
390
|
+
}
|
|
391
|
+
/** Welke context de app gebruikt. Bij het opstarten volgen we die van de CLI. */
|
|
392
|
+
let activeContext = (0, contexts_1.currentContextName)();
|
|
393
|
+
/**
|
|
394
|
+
* Claude, met dezelfde bronnen als de rest van de app. Hier en niet in assistant.ts, zodat die
|
|
395
|
+
* zonder Docker te testen is.
|
|
396
|
+
*/
|
|
397
|
+
const assistant = new assistant_1.AssistantService({
|
|
398
|
+
apiKey: () => (0, assistant_key_1.readApiKey)(),
|
|
399
|
+
model: () => (0, settings_1.assistantModel)(),
|
|
400
|
+
diagnose: (id) => (0, diagnosis_1.buildDiagnosis)(id, { appVersion: electron_1.app.getVersion(), context: activeContext }),
|
|
401
|
+
describeHost: async () => (0, host_overview_1.renderHostOverview)({
|
|
402
|
+
appVersion: electron_1.app.getVersion(),
|
|
403
|
+
context: activeContext,
|
|
404
|
+
engine: await (0, engine_1.readEngineStatus)(),
|
|
405
|
+
containers: await (0, containers_1.listContainers)().catch(() => []),
|
|
406
|
+
}),
|
|
407
|
+
terminal: (ownerId, lines) => hostTranscripts.get(ownerId)?.lines(lines) ?? null,
|
|
408
|
+
source: {
|
|
409
|
+
listContainers: () => (0, containers_1.listContainers)(),
|
|
410
|
+
inspect: async (id) => (await (0, docker_client_1.getConnection)().docker.getContainer(id).inspect()),
|
|
411
|
+
logs: (id, tty, tail) => (0, diagnosis_1.readLogs)(id, tty, tail),
|
|
412
|
+
events: (id) => (0, diagnosis_1.readEvents)(id, new Date()),
|
|
413
|
+
stats: async (id) => {
|
|
414
|
+
const raw = (await (0, docker_client_1.getConnection)().docker.getContainer(id).stats({ stream: false }));
|
|
415
|
+
const memory = (0, stats_1.toMemoryBytes)(raw);
|
|
416
|
+
return { cpuPercent: (0, stats_1.toCpuPercent)(raw), memoryBytes: memory.used, memoryLimitBytes: memory.limit };
|
|
417
|
+
},
|
|
418
|
+
},
|
|
419
|
+
});
|
|
420
|
+
function requireState(senderId) {
|
|
421
|
+
const state = windows.get(senderId);
|
|
422
|
+
if (state === undefined) {
|
|
423
|
+
throw new Error((0, i18n_1.t)({
|
|
424
|
+
en: 'This window has no stream registry.',
|
|
425
|
+
nl: 'Dit venster heeft geen streamregister.',
|
|
426
|
+
de: 'Dieses Fenster hat kein Streamregister.',
|
|
427
|
+
fr: 'Cette fenêtre n’a pas de registre de flux.',
|
|
428
|
+
es: 'Esta ventana no tiene registro de streams.',
|
|
429
|
+
}));
|
|
430
|
+
}
|
|
431
|
+
return state;
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* Zet de watchers en het streamregister van een venster opnieuw op.
|
|
435
|
+
*
|
|
436
|
+
* Nieuwe instanties en niet de oude opnieuw starten: `stop()` zet ze definitief stil, juist
|
|
437
|
+
* zodat een stream die nog aan het openen was niet alsnog binnenkomt nadat het venster weg is.
|
|
438
|
+
*/
|
|
439
|
+
function startWatchers(state) {
|
|
440
|
+
state.stats = new stats_1.StatsWatcher(state.sender);
|
|
441
|
+
state.mergedLogs = new merged_logs_1.MergedLogsWatcher(state.sender);
|
|
442
|
+
state.events = new events_1.EventsWatcher(state.sender);
|
|
443
|
+
state.streams = new streams_1.StreamRegistry(state.sender);
|
|
444
|
+
state.events.start();
|
|
445
|
+
}
|
|
446
|
+
function stopWatchers(state) {
|
|
447
|
+
state.stats.stop();
|
|
448
|
+
state.mergedLogs.stop();
|
|
449
|
+
state.events.stop();
|
|
450
|
+
state.streams.closeAll();
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Schakelt om naar een andere context.
|
|
454
|
+
*
|
|
455
|
+
* Dit is bewust één functie. Alles wat aan de oude daemon hing moet dicht voordat de nieuwe
|
|
456
|
+
* verbinding er staat: de stats-streams, de events-stream, de logvensters, een lopende pull en
|
|
457
|
+
* elke open shell. Laat je er één staan, dan blijft die stilletjes van de vorige daemon lezen
|
|
458
|
+
* en klopt de helft van wat je ziet niet meer.
|
|
459
|
+
*
|
|
460
|
+
* `~/.docker/config.json` blijft ongemoeid: de keuze geldt voor deze app, niet voor je terminal.
|
|
461
|
+
*/
|
|
462
|
+
async function useContext(name) {
|
|
463
|
+
const target = (0, contexts_1.listContexts)(activeContext).find((context) => context.name === name);
|
|
464
|
+
if (target === undefined) {
|
|
465
|
+
return { ok: false, problem: (0, i18n_1.t)({
|
|
466
|
+
en: `Context '${name}' does not exist (anymore).`,
|
|
467
|
+
nl: `Context '${name}' bestaat niet (meer).`,
|
|
468
|
+
de: `Context '${name}' existiert nicht (mehr).`,
|
|
469
|
+
fr: `Le context '${name}' n’existe pas (ou plus).`,
|
|
470
|
+
es: `El context '${name}' no existe (ya).`,
|
|
471
|
+
}) };
|
|
472
|
+
}
|
|
473
|
+
if (!target.supported) {
|
|
474
|
+
return { ok: false, problem: target.reason ?? (0, i18n_1.t)({
|
|
475
|
+
en: `Context '${name}' is not supported.`,
|
|
476
|
+
nl: `Context '${name}' wordt niet ondersteund.`,
|
|
477
|
+
de: `Context '${name}' wird nicht unterstützt.`,
|
|
478
|
+
fr: `Le context '${name}' n’est pas pris en charge.`,
|
|
479
|
+
es: `El context '${name}' no es compatible.`,
|
|
480
|
+
}) };
|
|
481
|
+
}
|
|
482
|
+
for (const state of windows.values()) {
|
|
483
|
+
stopWatchers(state);
|
|
484
|
+
}
|
|
485
|
+
execSessions.clear();
|
|
486
|
+
(0, docker_client_1.setConnection)((0, docker_client_1.connectionFromHost)(target.host, target.tlsDir));
|
|
487
|
+
activeContext = name;
|
|
488
|
+
(0, compose_1.resetComposeStatus)();
|
|
489
|
+
const status = await (0, engine_1.readEngineStatus)();
|
|
490
|
+
if (crashWatch !== null) {
|
|
491
|
+
startCrashWatch(crashWatch.onCrash, crashWatch.onWiring);
|
|
492
|
+
}
|
|
493
|
+
for (const state of windows.values()) {
|
|
494
|
+
startWatchers(state);
|
|
495
|
+
// Alles opnieuw ophalen: de stores luisteren al op dit kanaal, dus dit is genoeg om de
|
|
496
|
+
// hele app op de nieuwe daemon te zetten.
|
|
497
|
+
state.streams.send(channels_1.CHANNELS.dockerEvents, ['container', 'image', 'volume', 'network']);
|
|
498
|
+
}
|
|
499
|
+
return { ok: true, status };
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* Laat de verbinding het adres van de actieve context volgen.
|
|
503
|
+
*
|
|
504
|
+
* Zonder dit praat de app met wat DOCKER_HOST of het platform aangeeft, en dat is niet
|
|
505
|
+
* noodzakelijk waar de CLI naar wijst: op Docker Desktop luistert `default` op
|
|
506
|
+
* docker_engine en `desktop-linux` op dockerDesktopLinuxEngine. Beide werken, dus het valt
|
|
507
|
+
* niet op — tot je een tweede daemon hebt en de app naar de verkeerde kijkt.
|
|
508
|
+
*/
|
|
509
|
+
function applyActiveContext() {
|
|
510
|
+
const target = (0, contexts_1.listContexts)(activeContext).find((context) => context.name === activeContext);
|
|
511
|
+
if (target?.supported === true) {
|
|
512
|
+
(0, docker_client_1.setConnection)((0, docker_client_1.connectionFromHost)(target.host, target.tlsDir));
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
const hostKeys = new servers_1.HostKeyInbox();
|
|
516
|
+
/**
|
|
517
|
+
* Test een bewaarde context met zijn eigen certificaten, of een server uit het formulier. Van
|
|
518
|
+
* die laatste maken we het adres hier, met dezelfde controle als bij het bewaren: zo test je
|
|
519
|
+
* precies wat je straks toevoegt.
|
|
520
|
+
*/
|
|
521
|
+
function testTarget(target) {
|
|
522
|
+
if ('server' in target) {
|
|
523
|
+
const address = (0, contexts_1.serverHost)(target.server);
|
|
524
|
+
return 'problem' in address
|
|
525
|
+
? Promise.resolve({ ok: false, problem: address.problem, unknownHostKey: null })
|
|
526
|
+
: (0, servers_1.testServer)(address.host, null, hostKeys);
|
|
527
|
+
}
|
|
528
|
+
const context = (0, contexts_1.listContexts)(activeContext).find((candidate) => candidate.name === target.context);
|
|
529
|
+
if (context === undefined) {
|
|
530
|
+
return Promise.resolve({
|
|
531
|
+
ok: false,
|
|
532
|
+
problem: (0, i18n_1.t)({
|
|
533
|
+
en: `Context '${target.context}' does not exist (anymore).`,
|
|
534
|
+
nl: `Context '${target.context}' bestaat niet (meer).`,
|
|
535
|
+
de: `Context '${target.context}' existiert nicht (mehr).`,
|
|
536
|
+
fr: `Le context '${target.context}' n’existe pas (ou plus).`,
|
|
537
|
+
es: `El context '${target.context}' no existe (ya).`,
|
|
538
|
+
}),
|
|
539
|
+
unknownHostKey: null,
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
return (0, servers_1.testServer)(context.host, context.tlsDir, hostKeys);
|
|
543
|
+
}
|
|
544
|
+
/** Wat een shell in een container en een shell op de host delen: het stream-id en het opruimen. */
|
|
545
|
+
async function openShell(senderId, cols, rows, open) {
|
|
546
|
+
const state = requireState(senderId);
|
|
547
|
+
const size = { cols: terminalSize.parse(cols), rows: terminalSize.parse(rows) };
|
|
548
|
+
const reserved = state.streams.reserve('exec');
|
|
549
|
+
const outcome = await open(size, (chunk) => state.streams.send(`${channels_1.CHANNELS.execData}:${reserved}`, chunk), () => state.streams.send(`${channels_1.CHANNELS.execEnd}:${reserved}`, undefined));
|
|
550
|
+
if (!outcome.ok) {
|
|
551
|
+
state.streams.close(reserved);
|
|
552
|
+
return { ok: false, problem: outcome.problem };
|
|
553
|
+
}
|
|
554
|
+
execSessions.set(reserved, outcome.session);
|
|
555
|
+
state.streams.attach(reserved, {
|
|
556
|
+
destroy: () => {
|
|
557
|
+
outcome.session.destroy();
|
|
558
|
+
execSessions.delete(reserved);
|
|
559
|
+
},
|
|
560
|
+
});
|
|
561
|
+
return { ok: true, streamId: reserved, shell: outcome.session.shell };
|
|
562
|
+
}
|
|
563
|
+
/**
|
|
564
|
+
* `ipcMain.handle`, maar met een fout die de renderer kan tonen. Wat een handler gooit komt
|
|
565
|
+
* aan als "Error invoking remote method '…': " plus de melding; die melding maken we hier
|
|
566
|
+
* leesbaar, en de renderer haalt het voorvoegsel eraf.
|
|
567
|
+
*/
|
|
568
|
+
function handle(channel, listener) {
|
|
569
|
+
electron_1.ipcMain.handle(channel, async (event, ...args) => {
|
|
570
|
+
try {
|
|
571
|
+
return await listener(event, ...args);
|
|
572
|
+
}
|
|
573
|
+
catch (err) {
|
|
574
|
+
throw (0, docker_client_1.readableError)(err, (0, docker_client_1.getConnection)().endpoint);
|
|
575
|
+
}
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
/**
|
|
579
|
+
* De enige plek waar kanalen aan ipcMain hangen. Dat staat los van de domeinmodules zelf,
|
|
580
|
+
* om twee redenen: het is één lijst om te overzien welk oppervlak de renderer krijgt, en de
|
|
581
|
+
* domeinmodules blijven vrij van een import van 'electron' — dat module bestaat niet buiten
|
|
582
|
+
* Electron, en dus ook niet in een unittest.
|
|
583
|
+
*/
|
|
584
|
+
function registerIpcHandlers() {
|
|
585
|
+
applyActiveContext();
|
|
586
|
+
handle(channels_1.CHANNELS.engineStatus, () => (0, engine_1.readEngineStatus)());
|
|
587
|
+
handle(channels_1.CHANNELS.engineStartWsl, (_event, distro) => (0, engine_1.startWsl)(wslDistro.parse(distro)));
|
|
588
|
+
handle(channels_1.CHANNELS.engineStartWslDaemon, (_event, distro) => (0, engine_1.startWslDaemon)(wslDistro.parse(distro)));
|
|
589
|
+
handle(channels_1.CHANNELS.containersList, () => (0, containers_1.listContainers)());
|
|
590
|
+
handle(channels_1.CHANNELS.containersInspect, (_event, id) => (0, containers_1.inspectContainer)(containerId.parse(id)));
|
|
591
|
+
handle(channels_1.CHANNELS.containersHealth, (_event, id) => (0, health_1.readContainerHealth)(containerId.parse(id)));
|
|
592
|
+
handle(channels_1.CHANNELS.containersEvents, (_event, name) => history.eventsOf(resourceName.parse(name)));
|
|
593
|
+
handle(channels_1.CHANNELS.containersWiring, (_event, id) => (0, wiring_1.readContainerWiring)(containerId.parse(id)));
|
|
594
|
+
handle(channels_1.CHANNELS.containersAttention, () => readAttention());
|
|
595
|
+
handle(channels_1.CHANNELS.containersRunCommand, (_event, id) => (0, run_command_1.readRunCommand)(containerId.parse(id)));
|
|
596
|
+
handle(channels_1.CHANNELS.containersRestartPolicy, (_event, id) => (0, restart_policy_1.readRestartPolicy)(containerId.parse(id)));
|
|
597
|
+
handle(channels_1.CHANNELS.containersSetRestartPolicy, (_event, id, policy) => (0, restart_policy_1.applyRestartPolicy)(containerId.parse(id), restartPolicy.parse(policy)));
|
|
598
|
+
handle(channels_1.CHANNELS.containersDiagnose, (_event, id) => (0, diagnosis_1.buildDiagnosis)(containerId.parse(id), { appVersion: electron_1.app.getVersion(), context: activeContext }));
|
|
599
|
+
handle(channels_1.CHANNELS.containersAction, (_event, action, id) => (0, containers_1.runContainerAction)(containerAction.parse(action), containerId.parse(id)));
|
|
600
|
+
handle(channels_1.CHANNELS.containersWatchStats, async (event, ids) => {
|
|
601
|
+
await windows.get(event.sender.id)?.stats.watch(containerIds.parse(ids));
|
|
602
|
+
});
|
|
603
|
+
handle(channels_1.CHANNELS.imagesList, () => (0, images_1.listImages)());
|
|
604
|
+
handle(channels_1.CHANNELS.imagesHistory, (_event, reference) => (0, image_history_1.readImageHistory)(imageReference.parse(reference)));
|
|
605
|
+
handle(channels_1.CHANNELS.imagesRemove, (_event, reference, force) => (0, images_1.removeImage)(imageReference.parse(reference), truthy.parse(force)));
|
|
606
|
+
handle(channels_1.CHANNELS.imagesPrune, (_event, all) => (0, images_1.pruneImages)(truthy.parse(all)));
|
|
607
|
+
handle(channels_1.CHANNELS.imagesRun, (_event, options) => (0, run_1.runContainer)(runOptions.parse(options)));
|
|
608
|
+
handle(channels_1.CHANNELS.imagesPull, async (event, reference) => {
|
|
609
|
+
const state = requireState(event.sender.id);
|
|
610
|
+
const reserved = state.streams.reserve('pull');
|
|
611
|
+
const channel = `${channels_1.CHANNELS.imagesPullProgress}:${reserved}`;
|
|
612
|
+
const outcome = await (0, pull_1.openPullStream)(imageReference.parse(reference), (progress) => {
|
|
613
|
+
// Na een pull wijst de tag misschien ergens anders heen; het oude antwoord klopt dan niet
|
|
614
|
+
// meer. Alles wissen: `nginx` ophalen raakt ook wat als `nginx:latest` onthouden was.
|
|
615
|
+
if (progress.done) {
|
|
616
|
+
(0, updates_1.clearUpdateCache)();
|
|
617
|
+
}
|
|
618
|
+
state.streams.send(channel, progress);
|
|
619
|
+
});
|
|
620
|
+
if (!outcome.ok) {
|
|
621
|
+
// Het id weer vrijgeven; anders blijft er een reservering hangen waar nooit een stream
|
|
622
|
+
// bij komt.
|
|
623
|
+
state.streams.close(reserved);
|
|
624
|
+
return { ok: false, problem: outcome.problem };
|
|
625
|
+
}
|
|
626
|
+
state.streams.attach(reserved, outcome.stream);
|
|
627
|
+
return { ok: true, streamId: reserved };
|
|
628
|
+
});
|
|
629
|
+
handle(channels_1.CHANNELS.imagesCheckUpdates, () => (0, updates_1.checkImageUpdates)());
|
|
630
|
+
handle(channels_1.CHANNELS.imagesStale, (_event, reference) => (0, renew_1.listStaleContainers)(imageReference.parse(reference)));
|
|
631
|
+
handle(channels_1.CHANNELS.imagesRenew, (_event, reference) => (0, renew_1.renewContainers)(imageReference.parse(reference)));
|
|
632
|
+
handle(channels_1.CHANNELS.imagesPullClose, (event, id) => {
|
|
633
|
+
windows.get(event.sender.id)?.streams.close(streamId.parse(id));
|
|
634
|
+
});
|
|
635
|
+
handle(channels_1.CHANNELS.volumesList, () => (0, volumes_1.listVolumes)());
|
|
636
|
+
handle(channels_1.CHANNELS.volumesSizes, () => (0, volumes_1.readVolumeSizes)());
|
|
637
|
+
handle(channels_1.CHANNELS.volumesCreate, (_event, volume) => (0, volumes_1.createVolume)(newVolume.parse(volume)));
|
|
638
|
+
handle(channels_1.CHANNELS.volumesRemove, (_event, name, force) => (0, volumes_1.removeVolume)(resourceName.parse(name), truthy.parse(force)));
|
|
639
|
+
handle(channels_1.CHANNELS.volumesPrune, (_event, all) => (0, volumes_1.pruneVolumes)(truthy.parse(all)));
|
|
640
|
+
handle(channels_1.CHANNELS.volumesBrowse, (_event, name, dir) => (0, volume_files_1.listVolumeDirectory)(resourceName.parse(name), containerPath.parse(dir)));
|
|
641
|
+
handle(channels_1.CHANNELS.volumesCloseBrowser, () => (0, volume_files_1.closeVolumeBrowser)());
|
|
642
|
+
handle(channels_1.CHANNELS.volumesBackup, (event, name) => (0, volume_backup_1.backupVolume)(resourceName.parse(name), async (suggestedName) => {
|
|
643
|
+
const owner = electron_1.BrowserWindow.fromWebContents(event.sender);
|
|
644
|
+
const options = { defaultPath: suggestedName, filters: [{ name: (0, i18n_1.t)({ en: 'Backup', nl: 'Back-up', de: 'Sicherung', fr: 'Sauvegarde', es: 'Copia de seguridad' }), extensions: ['tar'] }] };
|
|
645
|
+
const choice = owner === null ? await electron_1.dialog.showSaveDialog(options) : await electron_1.dialog.showSaveDialog(owner, options);
|
|
646
|
+
return choice.canceled || choice.filePath === undefined ? null : choice.filePath;
|
|
647
|
+
}));
|
|
648
|
+
handle(channels_1.CHANNELS.volumesRestore, async (event, name) => {
|
|
649
|
+
const volume = resourceName.parse(name);
|
|
650
|
+
const owner = electron_1.BrowserWindow.fromWebContents(event.sender);
|
|
651
|
+
const options = { properties: ['openFile'], filters: [{ name: (0, i18n_1.t)({ en: 'Backup', nl: 'Back-up', de: 'Sicherung', fr: 'Sauvegarde', es: 'Copia de seguridad' }), extensions: ['tar'] }] };
|
|
652
|
+
const choice = owner === null ? await electron_1.dialog.showOpenDialog(options) : await electron_1.dialog.showOpenDialog(owner, options);
|
|
653
|
+
return (0, volume_backup_1.restoreVolume)(volume, choice.canceled ? null : (choice.filePaths[0] ?? null));
|
|
654
|
+
});
|
|
655
|
+
handle(channels_1.CHANNELS.networksList, () => (0, networks_1.listNetworks)());
|
|
656
|
+
handle(channels_1.CHANNELS.networksCreate, (_event, network) => (0, networks_1.createNetwork)(newNetwork.parse(network)));
|
|
657
|
+
handle(channels_1.CHANNELS.networksRemove, (_event, id) => (0, networks_1.removeNetwork)(resourceName.parse(id)));
|
|
658
|
+
handle(channels_1.CHANNELS.networksPrune, () => (0, networks_1.pruneNetworks)());
|
|
659
|
+
handle(channels_1.CHANNELS.networksMembers, (_event, name) => (0, networks_1.listNetworkMembers)(resourceName.parse(name)));
|
|
660
|
+
handle(channels_1.CHANNELS.networksOfContainer, (_event, id) => (0, networks_1.listContainerNetworks)(containerId.parse(id)));
|
|
661
|
+
handle(channels_1.CHANNELS.networksConnect, (_event, network, id, alias) => (0, networks_1.connectContainer)(resourceName.parse(network), containerId.parse(id), networkAlias.nullable().parse(alias)));
|
|
662
|
+
handle(channels_1.CHANNELS.networksDisconnect, (_event, network, id) => (0, networks_1.disconnectContainer)(resourceName.parse(network), containerId.parse(id)));
|
|
663
|
+
handle(channels_1.CHANNELS.composeStatus, () => (0, compose_1.readComposeStatus)());
|
|
664
|
+
handle(channels_1.CHANNELS.composeUp, async (_event, name) => withPortHolder(await (0, compose_1.runComposeCommand)(resourceName.parse(name), 'up')));
|
|
665
|
+
handle(channels_1.CHANNELS.composeDown, (_event, name) => (0, compose_1.runComposeCommand)(resourceName.parse(name), 'down'));
|
|
666
|
+
handle(channels_1.CHANNELS.composeRecreate, async (_event, name, service) => withPortHolder(await (0, compose_1.recreateService)(resourceName.parse(name), resourceName.parse(service))));
|
|
667
|
+
handle(channels_1.CHANNELS.composeFiles, (_event, name) => (0, compose_files_1.readProjectFiles)(resourceName.parse(name)));
|
|
668
|
+
handle(channels_1.CHANNELS.composeConfig, (_event, name) => (0, compose_files_1.readResolvedConfig)(resourceName.parse(name)));
|
|
669
|
+
handle(channels_1.CHANNELS.systemDiskUsage, () => (0, system_1.readDiskUsage)());
|
|
670
|
+
handle(channels_1.CHANNELS.systemPrune, (_event, selection) => (0, system_1.pruneSystem)(pruneSelection.parse(selection)));
|
|
671
|
+
handle(channels_1.CHANNELS.navigationTakePending, () => (0, navigation_1.takePendingRoute)());
|
|
672
|
+
handle(channels_1.CHANNELS.assistantStatus, () => ({ configured: (0, assistant_key_1.hasApiKey)(), storageAvailable: (0, assistant_key_1.keyStoreAvailable)() }));
|
|
673
|
+
handle(channels_1.CHANNELS.assistantSetKey, (_event, key) => (0, assistant_key_1.saveApiKey)(apiKey.parse(key)));
|
|
674
|
+
handle(channels_1.CHANNELS.assistantClearKey, () => {
|
|
675
|
+
(0, assistant_key_1.removeApiKey)();
|
|
676
|
+
return { configured: (0, assistant_key_1.hasApiKey)(), storageAvailable: (0, assistant_key_1.keyStoreAvailable)() };
|
|
677
|
+
});
|
|
678
|
+
handle(channels_1.CHANNELS.assistantStart, (event, id) => {
|
|
679
|
+
const sender = event.sender;
|
|
680
|
+
return assistant.start(sender.id, containerId.nullable().parse(id), (conversation, payload) => {
|
|
681
|
+
if (!sender.isDestroyed()) {
|
|
682
|
+
sender.send(`${channels_1.CHANNELS.assistantEvent}:${conversation}`, payload);
|
|
683
|
+
}
|
|
684
|
+
});
|
|
685
|
+
});
|
|
686
|
+
handle(channels_1.CHANNELS.assistantAsk, (event, id, text) => assistant.ask(event.sender.id, streamId.parse(id), question.parse(text)));
|
|
687
|
+
handle(channels_1.CHANNELS.assistantClose, (event, id) => assistant.close(event.sender.id, streamId.parse(id)));
|
|
688
|
+
handle(channels_1.CHANNELS.settingsGet, () => (0, settings_1.readSettings)());
|
|
689
|
+
handle(channels_1.CHANNELS.settingsUpdate, (_event, patch) => (0, settings_1.updateSettings)(settingsPatch.parse(patch)));
|
|
690
|
+
handle(channels_1.CHANNELS.appUpdateGet, () => (0, self_update_1.readAppUpdate)());
|
|
691
|
+
handle(channels_1.CHANNELS.appUpdateCheck, () => (0, self_update_1.checkForAppUpdate)());
|
|
692
|
+
// Via before-quit in main.ts, net als Afsluiten in het systeemvak: dan ruimt die ook de
|
|
693
|
+
// hulpcontainers van de host-terminal op.
|
|
694
|
+
handle(channels_1.CHANNELS.appQuit, () => electron_1.app.quit());
|
|
695
|
+
handle(channels_1.CHANNELS.appUpdateInstall, () => (0, self_update_1.installAppUpdate)());
|
|
696
|
+
handle(channels_1.CHANNELS.filesList, (_event, id, dir) => (0, files_1.listDirectory)(containerId.parse(id), containerPath.parse(dir)));
|
|
697
|
+
handle(channels_1.CHANNELS.filesChanges, (_event, id) => (0, files_1.listChanges)(containerId.parse(id)));
|
|
698
|
+
handle(channels_1.CHANNELS.filesDownload, (event, id, source, kind) => (0, files_1.downloadPath)(containerId.parse(id), containerPath.parse(source), fileKind.parse(kind), async (suggestedName) => {
|
|
699
|
+
const owner = electron_1.BrowserWindow.fromWebContents(event.sender);
|
|
700
|
+
const options = { defaultPath: suggestedName };
|
|
701
|
+
const choice = owner === null ? await electron_1.dialog.showSaveDialog(options) : await electron_1.dialog.showSaveDialog(owner, options);
|
|
702
|
+
return choice.canceled || choice.filePath === undefined ? null : choice.filePath;
|
|
703
|
+
}));
|
|
704
|
+
handle(channels_1.CHANNELS.filesUpload, (_event, id, dir, locals) => (0, files_1.uploadFiles)(containerId.parse(id), containerPath.parse(dir), localPaths.parse(locals)));
|
|
705
|
+
handle(channels_1.CHANNELS.filesChooseAndUpload, async (event, id, dir) => {
|
|
706
|
+
const owner = electron_1.BrowserWindow.fromWebContents(event.sender);
|
|
707
|
+
const options = { properties: ['openFile', 'multiSelections'] };
|
|
708
|
+
const choice = owner === null ? await electron_1.dialog.showOpenDialog(options) : await electron_1.dialog.showOpenDialog(owner, options);
|
|
709
|
+
if (choice.canceled) {
|
|
710
|
+
return { ok: true, count: 0 };
|
|
711
|
+
}
|
|
712
|
+
return (0, files_1.uploadFiles)(containerId.parse(id), containerPath.parse(dir), choice.filePaths);
|
|
713
|
+
});
|
|
714
|
+
handle(channels_1.CHANNELS.contextsList, () => (0, contexts_1.listContexts)(activeContext));
|
|
715
|
+
handle(channels_1.CHANNELS.contextsUse, (_event, name) => useContext(resourceName.parse(name)));
|
|
716
|
+
handle(channels_1.CHANNELS.contextsCreate, (_event, server) => (0, contexts_1.createContext)(newServer.parse(server)));
|
|
717
|
+
handle(channels_1.CHANNELS.contextsRemove, (_event, name) => (0, contexts_1.removeContext)(resourceName.parse(name), activeContext));
|
|
718
|
+
handle(channels_1.CHANNELS.contextsTest, (_event, target) => testTarget(serverTestTarget.parse(target)));
|
|
719
|
+
handle(channels_1.CHANNELS.contextsTrustHostKey, (_event, name, shown) => hostKeys.trust(hostKeyName.parse(name), hostKeyFingerprint.parse(shown)));
|
|
720
|
+
handle(channels_1.CHANNELS.execOpen, (event, id, cols, rows) => {
|
|
721
|
+
const target = containerId.parse(id);
|
|
722
|
+
return openShell(event.sender.id, cols, rows, (size, onData, onEnd) => (0, exec_1.openExec)(target, size, onData, onEnd));
|
|
723
|
+
});
|
|
724
|
+
handle(channels_1.CHANNELS.execOpenHost, (event, cols, rows) => {
|
|
725
|
+
const transcript = new terminal_transcript_1.TerminalTranscript();
|
|
726
|
+
const owner = event.sender.id;
|
|
727
|
+
hostTranscripts.set(owner, transcript);
|
|
728
|
+
return openShell(owner, cols, rows, (size, onData, onEnd) => (0, host_shell_1.openHostShell)(size, (chunk) => {
|
|
729
|
+
transcript.append(chunk);
|
|
730
|
+
onData(chunk);
|
|
731
|
+
}, onEnd));
|
|
732
|
+
});
|
|
733
|
+
handle(channels_1.CHANNELS.execWrite, (_event, id, data) => {
|
|
734
|
+
execSessions.get(streamId.parse(id))?.write(terminalInput.parse(data));
|
|
735
|
+
});
|
|
736
|
+
handle(channels_1.CHANNELS.execResize, (_event, id, cols, rows) => {
|
|
737
|
+
execSessions.get(streamId.parse(id))?.resize(terminalSize.parse(cols), terminalSize.parse(rows));
|
|
738
|
+
});
|
|
739
|
+
handle(channels_1.CHANNELS.execClose, (event, id) => {
|
|
740
|
+
windows.get(event.sender.id)?.streams.close(streamId.parse(id));
|
|
741
|
+
});
|
|
742
|
+
handle(channels_1.CHANNELS.logsOpen, async (event, id, tail) => {
|
|
743
|
+
const state = requireState(event.sender.id);
|
|
744
|
+
// Het id wordt uitgegeven vóór de stream bestaat, zodat een close() tijdens het openen
|
|
745
|
+
// niet in het luchtledige valt. Zie StreamRegistry.
|
|
746
|
+
const reserved = state.streams.reserve('logs');
|
|
747
|
+
const channel = `${channels_1.CHANNELS.logsLines}:${reserved}`;
|
|
748
|
+
const stream = await (0, logs_1.openLogStream)(containerId.parse(id), logTail.parse(tail), (lines) => {
|
|
749
|
+
state.streams.send(channel, lines);
|
|
750
|
+
});
|
|
751
|
+
state.streams.attach(reserved, stream);
|
|
752
|
+
return reserved;
|
|
753
|
+
});
|
|
754
|
+
handle(channels_1.CHANNELS.logsClose, (event, id) => {
|
|
755
|
+
windows.get(event.sender.id)?.streams.close(streamId.parse(id));
|
|
756
|
+
});
|
|
757
|
+
handle(channels_1.CHANNELS.logsWatchAll, async (event, sources) => {
|
|
758
|
+
await requireState(event.sender.id).mergedLogs.watch(logSources.parse(sources));
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
/**
|
|
762
|
+
* Hangt de streams aan één venster. Ze horen bij dat venster en niet bij de app: zonder het
|
|
763
|
+
* opruimen hieronder blijft een stats-, log- of execstream doorlopen nadat het venster weg is.
|
|
764
|
+
*/
|
|
765
|
+
function attachWindow(window) {
|
|
766
|
+
const sender = window.webContents;
|
|
767
|
+
const state = {
|
|
768
|
+
sender,
|
|
769
|
+
stats: new stats_1.StatsWatcher(sender),
|
|
770
|
+
mergedLogs: new merged_logs_1.MergedLogsWatcher(sender),
|
|
771
|
+
events: new events_1.EventsWatcher(sender),
|
|
772
|
+
streams: new streams_1.StreamRegistry(sender),
|
|
773
|
+
};
|
|
774
|
+
windows.set(sender.id, state);
|
|
775
|
+
state.events.start();
|
|
776
|
+
sender.once('destroyed', () => {
|
|
777
|
+
stopWatchers(state);
|
|
778
|
+
assistant.closeOwner(sender.id);
|
|
779
|
+
hostTranscripts.delete(sender.id);
|
|
780
|
+
windows.delete(sender.id);
|
|
781
|
+
});
|
|
782
|
+
}
|
|
783
|
+
//# sourceMappingURL=ipc.js.map
|