claw-dashboard 1.8.4 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.c8rc.json +38 -0
- package/.dockerignore +68 -0
- package/.github/dependabot.yml +45 -0
- package/.github/pull_request_template.md +39 -0
- package/.github/workflows/ci.yml +147 -0
- package/.github/workflows/release.yml +40 -0
- package/.github/workflows/security.yml +84 -0
- package/.husky/pre-commit +1 -0
- package/.planning/codebase/ARCHITECTURE.md +206 -0
- package/.planning/codebase/INTEGRATIONS.md +150 -0
- package/.planning/codebase/STACK.md +122 -0
- package/.planning/codebase/STRUCTURE.md +201 -0
- package/CHANGELOG.md +293 -0
- package/CONTRIBUTING.md +378 -0
- package/Dockerfile +54 -0
- package/FEATURES.md +195 -21
- package/README.md +83 -6
- package/TODO.md +28 -0
- package/build-cjs.js +127 -0
- package/cjs-shim.js +13 -0
- package/dist/clawdash +1729 -0
- package/dist/clawdash.meta.json +2236 -0
- package/dist/widgets.cjs +6511 -0
- package/docker-compose.yml +67 -0
- package/docs/API.md +1050 -0
- package/docs/PLUGINS.md +1504 -0
- package/esbuild.config.js +158 -0
- package/eslint.config.js +56 -0
- package/examples/plugins/README.md +122 -0
- package/examples/plugins/api-status/index.js +294 -0
- package/examples/plugins/api-status/plugin.json +19 -0
- package/examples/plugins/hello-world/index.js +104 -0
- package/examples/plugins/hello-world/plugin.json +15 -0
- package/examples/plugins/system-metrics-chart/index.js +339 -0
- package/examples/plugins/system-metrics-chart/plugin.json +18 -0
- package/examples/plugins/weather-widget/index.js +136 -0
- package/examples/plugins/weather-widget/plugin.json +19 -0
- package/index.cjs +23005 -0
- package/index.js +5331 -512
- package/jest.config.js +11 -0
- package/man/clawdash.1 +276 -0
- package/package.json +54 -5
- package/schemas/plugin-manifest.json +128 -0
- package/scripts/release.js +595 -0
- package/src/alerts.js +693 -0
- package/src/auto-save.js +584 -0
- package/src/cache.js +390 -0
- package/src/checksum.js +146 -0
- package/src/cli/args.js +110 -0
- package/src/cli/export-schedule.js +423 -0
- package/src/cli/export-snapshot.js +138 -0
- package/src/cli/help.js +69 -0
- package/src/cli/import-snapshot.js +230 -0
- package/src/cli/index.js +14 -0
- package/src/cli/list-templates.js +69 -0
- package/src/cli/validate-config.js +76 -0
- package/src/cli/validate-plugin.js +187 -0
- package/src/cli/version.js +15 -0
- package/src/config-validator.js +586 -0
- package/src/config-watcher.js +401 -0
- package/src/config.js +684 -0
- package/src/container-detector.js +499 -0
- package/src/database.js +734 -0
- package/src/differential-render.js +327 -0
- package/src/errors.js +169 -0
- package/src/export-scheduler.js +581 -0
- package/src/gateway-manager.js +685 -0
- package/src/hints.js +371 -0
- package/src/loading-states.js +509 -0
- package/src/logger.js +185 -0
- package/src/memory-pressure.js +467 -0
- package/src/performance-monitor.js +374 -0
- package/src/plugin-errors.js +586 -0
- package/src/plugin-manifest-validator.js +219 -0
- package/src/plugin-reload.js +481 -0
- package/src/plugin-scaffold.js +1941 -0
- package/src/plugin-validator.js +284 -0
- package/src/retry.js +218 -0
- package/src/security.js +860 -0
- package/src/snapshot.js +372 -0
- package/src/splash.js +115 -0
- package/src/theme-selector.js +411 -0
- package/src/themes.js +874 -0
- package/src/transitions.js +534 -0
- package/src/types.d.ts +174 -0
- package/src/validation.js +926 -0
- package/src/web-server.js +885 -0
- package/src/widgets/builtin-widgets.js +1057 -0
- package/src/widgets/config-processor.js +401 -0
- package/src/widgets/dependency-resolver.js +596 -0
- package/src/widgets/index.js +79 -0
- package/src/widgets/plugin-api.js +845 -0
- package/src/widgets/widget-error-boundary.js +773 -0
- package/src/widgets/widget-error-isolation.js +551 -0
- package/src/widgets/widget-loader.js +1437 -0
- package/src/workers/system-worker.js +130 -0
- package/src/workers/worker-pool.js +633 -0
- package/tests/alerts.test.js +437 -0
- package/tests/auto-save.test.js +529 -0
- package/tests/cache.test.js +317 -0
- package/tests/cli.test.js +351 -0
- package/tests/command-palette.test.js +320 -0
- package/tests/config-processor.test.js +452 -0
- package/tests/config-validator.test.js +710 -0
- package/tests/config-watcher.test.js +594 -0
- package/tests/config.test.js +755 -0
- package/tests/database.test.js +438 -0
- package/tests/errors.test.js +189 -0
- package/tests/example-plugins.test.js +624 -0
- package/tests/integration.test.js +1321 -0
- package/tests/loading-states.test.js +300 -0
- package/tests/manifest-validation-on-load.test.js +671 -0
- package/tests/performance-monitor.test.js +190 -0
- package/tests/plugin-api-rate-limit.test.js +302 -0
- package/tests/plugin-errors.test.js +311 -0
- package/tests/plugin-lifecycle-e2e.test.js +1036 -0
- package/tests/plugin-reload.test.js +764 -0
- package/tests/rate-limiter.test.js +539 -0
- package/tests/retry.test.js +308 -0
- package/tests/security.test.js +411 -0
- package/tests/settings-modal.test.js +226 -0
- package/tests/theme-selector.test.js +57 -0
- package/tests/utils.js +242 -0
- package/tests/utils.test.js +317 -0
- package/tests/validate-plugin-cli.test.js +359 -0
- package/tests/validation.test.js +837 -0
- package/tests/web-server.test.js +646 -0
- package/tests/widget-arrange-mode.test.js +183 -0
- package/tests/widget-config-hot-reload.test.js +465 -0
- package/tests/widget-dependency.test.js +591 -0
- package/tests/widget-error-boundary.test.js +749 -0
- package/tests/widget-error-isolation.test.js +469 -0
- package/tests/widget-integration.test.js +1147 -0
- package/tests/widget-refresh-intervals.test.js +284 -0
- package/tests/worker-pool.test.js +522 -0
|
@@ -0,0 +1,499 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Container Environment Detection Module
|
|
3
|
+
* Detects if running inside Docker, Kubernetes, or other containerized environments
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import fs from 'fs';
|
|
7
|
+
import os from 'os';
|
|
8
|
+
import { exec } from 'child_process';
|
|
9
|
+
import { promisify } from 'util';
|
|
10
|
+
import logger from './logger.js';
|
|
11
|
+
|
|
12
|
+
const execAsync = promisify(exec);
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Container environment types
|
|
16
|
+
* @typedef {Object} ContainerEnvironment
|
|
17
|
+
* @property {boolean} isContainer - Whether running in any container
|
|
18
|
+
* @property {boolean} isDocker - Whether running in Docker
|
|
19
|
+
* @property {boolean} isKubernetes - Whether running in Kubernetes
|
|
20
|
+
* @property {boolean} isWSL - Whether running in Windows Subsystem for Linux
|
|
21
|
+
* @property {number} wslVersion - WSL version (1 or 2) if isWSL is true, 0 otherwise
|
|
22
|
+
* @property {string|null} wslDistro - WSL distribution name if available
|
|
23
|
+
* @property {string|null} containerId - Container ID if detected
|
|
24
|
+
* @property {string|null} containerName - Container name if available
|
|
25
|
+
* @property {string|null} podName - Kubernetes pod name if available
|
|
26
|
+
* @property {string|null} namespace - Kubernetes namespace if available
|
|
27
|
+
* @property {string} runtime - Detected container runtime (docker, containerd, cri-o, etc.)
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Default container environment state
|
|
32
|
+
* @type {ContainerEnvironment}
|
|
33
|
+
*/
|
|
34
|
+
const DEFAULT_CONTAINER_ENV = {
|
|
35
|
+
isContainer: false,
|
|
36
|
+
isDocker: false,
|
|
37
|
+
isKubernetes: false,
|
|
38
|
+
isWSL: false,
|
|
39
|
+
wslVersion: 0,
|
|
40
|
+
wslDistro: null,
|
|
41
|
+
containerId: null,
|
|
42
|
+
containerName: null,
|
|
43
|
+
podName: null,
|
|
44
|
+
namespace: null,
|
|
45
|
+
runtime: 'none'
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Cached container environment detection result
|
|
50
|
+
* @type {ContainerEnvironment|null}
|
|
51
|
+
*/
|
|
52
|
+
let cachedContainerEnv = null;
|
|
53
|
+
let cacheTimestamp = 0;
|
|
54
|
+
const CACHE_TTL_MS = 30000; // 30 seconds cache
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Check if running inside a Docker container by examining cgroup
|
|
58
|
+
* @returns {Promise<boolean>}
|
|
59
|
+
*/
|
|
60
|
+
async function checkDockerCgroup() {
|
|
61
|
+
try {
|
|
62
|
+
const cgroupContent = fs.readFileSync('/proc/self/cgroup', 'utf8');
|
|
63
|
+
return cgroupContent.includes('docker') ||
|
|
64
|
+
cgroupContent.includes('containerd') ||
|
|
65
|
+
cgroupContent.includes('crio') ||
|
|
66
|
+
/[0-9a-f]{64}/.test(cgroupContent); // Container ID pattern
|
|
67
|
+
} catch {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Check if .dockerenv file exists (older Docker detection method)
|
|
74
|
+
* @returns {boolean}
|
|
75
|
+
*/
|
|
76
|
+
function checkDockerEnvFile() {
|
|
77
|
+
try {
|
|
78
|
+
fs.accessSync('/.dockerenv', fs.constants.F_OK);
|
|
79
|
+
return true;
|
|
80
|
+
} catch {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Check for Kubernetes-specific environment variables and files
|
|
87
|
+
* @returns {Promise<{isKubernetes: boolean, podName: string|null, namespace: string|null}>}
|
|
88
|
+
*/
|
|
89
|
+
async function checkKubernetes() {
|
|
90
|
+
const result = {
|
|
91
|
+
isKubernetes: false,
|
|
92
|
+
podName: null,
|
|
93
|
+
namespace: null
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
// Check for Kubernetes service account directory
|
|
98
|
+
if (fs.existsSync('/var/run/secrets/kubernetes.io')) {
|
|
99
|
+
result.isKubernetes = true;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Check environment variables
|
|
103
|
+
if (process.env.KUBERNETES_SERVICE_HOST || process.env.KUBERNETES_PORT) {
|
|
104
|
+
result.isKubernetes = true;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Get pod name from hostname (typically pod name in K8s)
|
|
108
|
+
if (process.env.HOSTNAME) {
|
|
109
|
+
// K8s hostnames often contain pod names
|
|
110
|
+
const hostname = process.env.HOSTNAME;
|
|
111
|
+
if (hostname.includes('-') && /[a-f0-9]{5,10}$/.test(hostname)) {
|
|
112
|
+
result.podName = hostname;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Get namespace from service account namespace file
|
|
117
|
+
try {
|
|
118
|
+
const namespacePath = '/var/run/secrets/kubernetes.io/serviceaccount/namespace';
|
|
119
|
+
if (fs.existsSync(namespacePath)) {
|
|
120
|
+
result.namespace = fs.readFileSync(namespacePath, 'utf8').trim();
|
|
121
|
+
}
|
|
122
|
+
} catch {
|
|
123
|
+
// Ignore read errors
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Also check env var
|
|
127
|
+
if (!result.namespace && process.env.KUBERNETES_NAMESPACE) {
|
|
128
|
+
result.namespace = process.env.KUBERNETES_NAMESPACE;
|
|
129
|
+
}
|
|
130
|
+
} catch {
|
|
131
|
+
// Ignore errors
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return result;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Check if running in Windows Subsystem for Linux (WSL)
|
|
139
|
+
* @returns {boolean}
|
|
140
|
+
*/
|
|
141
|
+
function checkWSL() {
|
|
142
|
+
// Check for WSL in /proc/version
|
|
143
|
+
try {
|
|
144
|
+
const version = fs.readFileSync('/proc/version', 'utf8').toLowerCase();
|
|
145
|
+
if (version.includes('microsoft') || version.includes('wsl')) {
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
} catch {
|
|
149
|
+
// Ignore errors
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Check for WSL-specific environment variable
|
|
153
|
+
if (process.env.WSL_DISTRO_NAME || process.env.WSLENV) {
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Check for Windows interop path
|
|
158
|
+
try {
|
|
159
|
+
if (fs.existsSync('/mnt/c/Windows')) {
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
} catch {
|
|
163
|
+
// Ignore errors
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Detect WSL version (1 or 2)
|
|
171
|
+
* WSL1: Uses Windows kernel, no systemd, /proc/version has "Microsoft" but no "WSL2"
|
|
172
|
+
* WSL2: Uses Linux kernel in VM, has systemd, /proc/version has "WSL2" or "microsoft-standard-WSL2"
|
|
173
|
+
* @returns {number} WSL version (1 or 2), 0 if not in WSL
|
|
174
|
+
*/
|
|
175
|
+
function detectWSLVersion() {
|
|
176
|
+
if (!checkWSL()) {
|
|
177
|
+
return 0;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Check /proc/version for WSL2-specific strings
|
|
181
|
+
try {
|
|
182
|
+
const version = fs.readFileSync('/proc/version', 'utf8').toLowerCase();
|
|
183
|
+
// WSL2 typically has "wsl2" or "microsoft-standard-wsl2" in the version string
|
|
184
|
+
if (version.includes('wsl2') || version.includes('microsoft-standard')) {
|
|
185
|
+
return 2;
|
|
186
|
+
}
|
|
187
|
+
} catch {
|
|
188
|
+
// Ignore errors
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Check if systemd is available (WSL2 usually has systemd, WSL1 does not)
|
|
192
|
+
try {
|
|
193
|
+
if (fs.existsSync('/run/systemd/system')) {
|
|
194
|
+
return 2;
|
|
195
|
+
}
|
|
196
|
+
} catch {
|
|
197
|
+
// Ignore errors
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Check for WSL2-specific kernel features
|
|
201
|
+
// WSL2 kernels typically have version 4.19.x or higher
|
|
202
|
+
try {
|
|
203
|
+
const version = fs.readFileSync('/proc/version', 'utf8');
|
|
204
|
+
const kernelMatch = version.match(/Linux version (\d+)\.(\d+)/);
|
|
205
|
+
if (kernelMatch) {
|
|
206
|
+
const major = parseInt(kernelMatch[1]);
|
|
207
|
+
const minor = parseInt(kernelMatch[2]);
|
|
208
|
+
// WSL2 typically has kernel 4.19+ or 5.x, while WSL1 uses a simulated 2.6 or 3.x
|
|
209
|
+
if (major > 4 || (major === 4 && minor >= 19)) {
|
|
210
|
+
return 2;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
} catch {
|
|
214
|
+
// Ignore errors
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// If we detected WSL but not WSL2 specific markers, it's likely WSL1
|
|
218
|
+
return 1;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Get the WSL distribution name
|
|
223
|
+
* @returns {string|null}
|
|
224
|
+
*/
|
|
225
|
+
function getWSLDistroName() {
|
|
226
|
+
// Check WSL_DISTRO_NAME environment variable (available in WSL2)
|
|
227
|
+
if (process.env.WSL_DISTRO_NAME) {
|
|
228
|
+
return process.env.WSL_DISTRO_NAME;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Try to get from /etc/os-release
|
|
232
|
+
try {
|
|
233
|
+
const osRelease = fs.readFileSync('/etc/os-release', 'utf8');
|
|
234
|
+
const nameMatch = osRelease.match(/PRETTY_NAME="([^"]+)"/);
|
|
235
|
+
if (nameMatch) {
|
|
236
|
+
return nameMatch[1];
|
|
237
|
+
}
|
|
238
|
+
} catch {
|
|
239
|
+
// Ignore errors
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Get container ID from cgroup
|
|
247
|
+
* @returns {string|null}
|
|
248
|
+
*/
|
|
249
|
+
function getContainerId() {
|
|
250
|
+
try {
|
|
251
|
+
const cgroupContent = fs.readFileSync('/proc/self/cgroup', 'utf8');
|
|
252
|
+
|
|
253
|
+
// Try to match container ID pattern (64 hex characters)
|
|
254
|
+
const match = cgroupContent.match(/[0-9a-f]{64}/);
|
|
255
|
+
if (match) {
|
|
256
|
+
return match[0].substring(0, 12); // Short form like Docker
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Try alternative patterns for different container runtimes
|
|
260
|
+
const lines = cgroupContent.split('\n');
|
|
261
|
+
for (const line of lines) {
|
|
262
|
+
// Match patterns like /docker/<id> or /containerd/<id>
|
|
263
|
+
const dockerMatch = line.match(/\/docker\/([0-9a-f]{12,64})/i);
|
|
264
|
+
if (dockerMatch) {
|
|
265
|
+
return dockerMatch[1].substring(0, 12);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const containerdMatch = line.match(/\/containerd\/.*\/([0-9a-f]{12,64})/i);
|
|
269
|
+
if (containerdMatch) {
|
|
270
|
+
return containerdMatch[1].substring(0, 12);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const criMatch = line.match(/\/cri-containerd\/([0-9a-f]{12,64})/i);
|
|
274
|
+
if (criMatch) {
|
|
275
|
+
return criMatch[1].substring(0, 12);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
} catch {
|
|
279
|
+
// Ignore errors
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Get container name from Docker if available
|
|
287
|
+
* @returns {Promise<string|null>}
|
|
288
|
+
*/
|
|
289
|
+
async function getContainerName() {
|
|
290
|
+
// Try environment variable first
|
|
291
|
+
if (process.env.CONTAINER_NAME) {
|
|
292
|
+
return process.env.CONTAINER_NAME;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (process.env.HOSTNAME && !checkWSL()) {
|
|
296
|
+
// In containers, hostname is often the container ID
|
|
297
|
+
// But could also be a custom name
|
|
298
|
+
return process.env.HOSTNAME;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return null;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Detect container runtime from cgroup or process tree
|
|
306
|
+
* @returns {Promise<string>}
|
|
307
|
+
*/
|
|
308
|
+
async function detectRuntime() {
|
|
309
|
+
try {
|
|
310
|
+
const cgroupContent = fs.readFileSync('/proc/self/cgroup', 'utf8');
|
|
311
|
+
|
|
312
|
+
if (cgroupContent.includes('docker')) {
|
|
313
|
+
return 'docker';
|
|
314
|
+
}
|
|
315
|
+
if (cgroupContent.includes('containerd')) {
|
|
316
|
+
return 'containerd';
|
|
317
|
+
}
|
|
318
|
+
if (cgroupContent.includes('crio')) {
|
|
319
|
+
return 'cri-o';
|
|
320
|
+
}
|
|
321
|
+
if (cgroupContent.includes('podman')) {
|
|
322
|
+
return 'podman';
|
|
323
|
+
}
|
|
324
|
+
if (cgroupContent.includes('lxc')) {
|
|
325
|
+
return 'lxc';
|
|
326
|
+
}
|
|
327
|
+
if (cgroupContent.includes('systemd-nspawn')) {
|
|
328
|
+
return 'systemd-nspawn';
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Check if any container-specific files exist
|
|
332
|
+
if (fs.existsSync('/run/containerd')) {
|
|
333
|
+
return 'containerd';
|
|
334
|
+
}
|
|
335
|
+
if (fs.existsSync('/run/crio')) {
|
|
336
|
+
return 'cri-o';
|
|
337
|
+
}
|
|
338
|
+
if (fs.existsSync('/run/docker.sock') || fs.existsSync('/var/run/docker.sock')) {
|
|
339
|
+
// Can access Docker socket (might be mounted in container)
|
|
340
|
+
return 'docker-accessible';
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Default to generic container if cgroup suggests it
|
|
344
|
+
if (cgroupContent.includes('0::/') && cgroupContent.split('\n').length > 1) {
|
|
345
|
+
return 'container';
|
|
346
|
+
}
|
|
347
|
+
} catch {
|
|
348
|
+
// Ignore errors
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
return 'unknown';
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Detect containerized environment
|
|
356
|
+
* @returns {Promise<ContainerEnvironment>}
|
|
357
|
+
*/
|
|
358
|
+
export async function detectContainerEnv() {
|
|
359
|
+
// Return cached result if still valid
|
|
360
|
+
const now = Date.now();
|
|
361
|
+
if (cachedContainerEnv && (now - cacheTimestamp) < CACHE_TTL_MS) {
|
|
362
|
+
return cachedContainerEnv;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const env = { ...DEFAULT_CONTAINER_ENV };
|
|
366
|
+
|
|
367
|
+
// Skip detection on Windows (not containers in the Linux sense)
|
|
368
|
+
const platform = os.platform();
|
|
369
|
+
if (platform === 'win32') {
|
|
370
|
+
return env;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
try {
|
|
374
|
+
// Check for Docker
|
|
375
|
+
const [dockerCgroup, dockerEnvFile] = await Promise.all([
|
|
376
|
+
checkDockerCgroup(),
|
|
377
|
+
Promise.resolve(checkDockerEnvFile())
|
|
378
|
+
]);
|
|
379
|
+
|
|
380
|
+
if (dockerCgroup || dockerEnvFile) {
|
|
381
|
+
env.isContainer = true;
|
|
382
|
+
env.isDocker = true;
|
|
383
|
+
env.containerId = getContainerId();
|
|
384
|
+
env.containerName = await getContainerName();
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Check for Kubernetes
|
|
388
|
+
const k8sInfo = await checkKubernetes();
|
|
389
|
+
if (k8sInfo.isKubernetes) {
|
|
390
|
+
env.isContainer = true;
|
|
391
|
+
env.isKubernetes = true;
|
|
392
|
+
env.podName = k8sInfo.podName;
|
|
393
|
+
env.namespace = k8sInfo.namespace;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Check for WSL and detect version
|
|
397
|
+
env.isWSL = checkWSL();
|
|
398
|
+
if (env.isWSL) {
|
|
399
|
+
env.wslVersion = detectWSLVersion();
|
|
400
|
+
env.wslDistro = getWSLDistroName();
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Detect runtime if in a container
|
|
404
|
+
if (env.isContainer) {
|
|
405
|
+
env.runtime = await detectRuntime();
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Cache result
|
|
409
|
+
cachedContainerEnv = env;
|
|
410
|
+
cacheTimestamp = now;
|
|
411
|
+
|
|
412
|
+
} catch (err) {
|
|
413
|
+
logger.warn(`Container detection failed: ${err.message}`);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
return env;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* Get a human-readable description of the container environment
|
|
421
|
+
* @param {ContainerEnvironment} env
|
|
422
|
+
* @returns {string}
|
|
423
|
+
*/
|
|
424
|
+
export function getContainerDescription(env) {
|
|
425
|
+
if (!env.isContainer) {
|
|
426
|
+
return 'Bare Metal/VM';
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
const parts = [];
|
|
430
|
+
|
|
431
|
+
if (env.isKubernetes) {
|
|
432
|
+
parts.push('Kubernetes');
|
|
433
|
+
if (env.namespace) {
|
|
434
|
+
parts.push(`ns:${env.namespace}`);
|
|
435
|
+
}
|
|
436
|
+
if (env.podName) {
|
|
437
|
+
const shortPod = env.podName.length > 20
|
|
438
|
+
? env.podName.substring(0, 17) + '...'
|
|
439
|
+
: env.podName;
|
|
440
|
+
parts.push(`pod:${shortPod}`);
|
|
441
|
+
}
|
|
442
|
+
} else if (env.isDocker) {
|
|
443
|
+
parts.push('Docker');
|
|
444
|
+
if (env.containerId) {
|
|
445
|
+
parts.push(`id:${env.containerId}`);
|
|
446
|
+
}
|
|
447
|
+
} else {
|
|
448
|
+
parts.push(env.runtime !== 'unknown' ? env.runtime : 'Container');
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
if (env.isWSL) {
|
|
452
|
+
const wslLabel = env.wslVersion === 2 ? 'WSL2' : env.wslVersion === 1 ? 'WSL1' : 'WSL';
|
|
453
|
+
parts.push(`(${wslLabel})`);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
return parts.join(' ');
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Get short container indicator for system widget
|
|
461
|
+
* @param {ContainerEnvironment} env
|
|
462
|
+
* @returns {string}
|
|
463
|
+
*/
|
|
464
|
+
export function getContainerIndicator(env) {
|
|
465
|
+
// Show WSL indicator if running in WSL (even without container)
|
|
466
|
+
if (env.isWSL) {
|
|
467
|
+
const wslLabel = env.wslVersion === 2 ? 'WSL2' : env.wslVersion === 1 ? 'WSL1' : 'WSL';
|
|
468
|
+
return `⊞ ${wslLabel}`;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
if (!env.isContainer) {
|
|
472
|
+
return '';
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
if (env.isKubernetes) {
|
|
476
|
+
return '☸ K8s';
|
|
477
|
+
}
|
|
478
|
+
if (env.isDocker) {
|
|
479
|
+
return '🐳 Docker';
|
|
480
|
+
}
|
|
481
|
+
return '⬡ Container';
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Clear the container detection cache
|
|
486
|
+
* Forces fresh detection on next call
|
|
487
|
+
*/
|
|
488
|
+
export function clearContainerCache() {
|
|
489
|
+
cachedContainerEnv = null;
|
|
490
|
+
cacheTimestamp = 0;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
export default {
|
|
494
|
+
detectContainerEnv,
|
|
495
|
+
getContainerDescription,
|
|
496
|
+
getContainerIndicator,
|
|
497
|
+
clearContainerCache,
|
|
498
|
+
DEFAULT_CONTAINER_ENV
|
|
499
|
+
};
|