-
-
Notifications
You must be signed in to change notification settings - Fork 827
Expand file tree
/
Copy pathutil-common.ts
More file actions
430 lines (369 loc) · 12.2 KB
/
Copy pathutil-common.ts
File metadata and controls
430 lines (369 loc) · 12.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
/*
* Common utilities for backend and frontend
*/
import yaml, { Document, Pair, Scalar } from "yaml";
import { DotenvParseOutput } from "dotenv";
// Init dayjs
import dayjs from "dayjs";
import timezone from "dayjs/plugin/timezone";
import utc from "dayjs/plugin/utc";
import relativeTime from "dayjs/plugin/relativeTime";
// @ts-ignore
import { replaceVariablesSync } from "@inventage/envsubst";
dayjs.extend(utc);
dayjs.extend(timezone);
dayjs.extend(relativeTime);
export interface LooseObject {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[key: string]: any
}
export interface BaseRes {
ok: boolean;
msg?: string;
}
let randomBytes : (numBytes: number) => Uint8Array;
initRandomBytes();
async function initRandomBytes() {
if (typeof window !== "undefined" && window.crypto) {
randomBytes = function randomBytes(numBytes: number) {
const bytes = new Uint8Array(numBytes);
for (let i = 0; i < numBytes; i += 65536) {
window.crypto.getRandomValues(bytes.subarray(i, i + Math.min(numBytes - i, 65536)));
}
return bytes;
};
} else {
randomBytes = (await import("node:crypto")).randomBytes;
}
}
export const ALL_ENDPOINTS = "##ALL_DOCKGE_ENDPOINTS##";
// Stack Status
export const UNKNOWN = 0;
export const CREATED_FILE = 1;
export const CREATED_STACK = 2;
export const RUNNING = 3;
export const EXITED = 4;
export function statusName(status : number) : string {
switch (status) {
case CREATED_FILE:
return "draft";
case CREATED_STACK:
return "created_stack";
case RUNNING:
return "running";
case EXITED:
return "exited";
default:
return "unknown";
}
}
export function statusNameShort(status : number) : string {
switch (status) {
case CREATED_FILE:
return "I";
case CREATED_STACK:
return "I";
case RUNNING:
return "A";
case EXITED:
return "E";
default:
return "?";
}
}
export function statusColor(status : number) : string {
switch (status) {
case CREATED_FILE:
return "dark";
case CREATED_STACK:
return "dark";
case RUNNING:
return "primary";
case EXITED:
return "danger";
default:
return "secondary";
}
}
export const isDev = process.env.NODE_ENV === "development";
export const TERMINAL_COLS = 105;
export const TERMINAL_ROWS = 15;
export const PROGRESS_TERMINAL_ROWS = 15;
export const COMBINED_TERMINAL_COLS = 120;
export const COMBINED_TERMINAL_ROWS = 40;
export const ERROR_TYPE_VALIDATION = 1;
export const acceptedComposeFileNames = [
"compose.yaml",
"docker-compose.yaml",
"docker-compose.yml",
"compose.yml",
];
/**
* Generate a decimal integer number from a string
* @param str Input
* @param length Default is 10 which means 0 - 9
*/
export function intHash(str : string, length = 10) : number {
// A simple hashing function (you can use more complex hash functions if needed)
let hash = 0;
for (let i = 0; i < str.length; i++) {
hash += str.charCodeAt(i);
}
// Normalize the hash to the range [0, 10]
return (hash % length + length) % length; // Ensure the result is non-negative
}
/**
* Delays for specified number of seconds
* @param ms Number of milliseconds to sleep for
*/
export function sleep(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms));
}
/**
* Generate a random alphanumeric string of fixed length
* @param length Length of string to generate
* @returns string
*/
export function genSecret(length = 64) {
let secret = "";
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
const charsLength = chars.length;
for ( let i = 0; i < length; i++ ) {
secret += chars.charAt(getCryptoRandomInt(0, charsLength - 1));
}
return secret;
}
/**
* Get a random integer suitable for use in cryptography between upper
* and lower bounds.
* @param min Minimum value of integer
* @param max Maximum value of integer
* @returns Cryptographically suitable random integer
*/
export function getCryptoRandomInt(min: number, max: number):number {
// synchronous version of: https://github.com/joepie91/node-random-number-csprng
const range = max - min;
if (range >= Math.pow(2, 32)) {
console.log("Warning! Range is too large.");
}
let tmpRange = range;
let bitsNeeded = 0;
let bytesNeeded = 0;
let mask = 1;
while (tmpRange > 0) {
if (bitsNeeded % 8 === 0) {
bytesNeeded += 1;
}
bitsNeeded += 1;
mask = mask << 1 | 1;
tmpRange = tmpRange >>> 1;
}
const bytes = randomBytes(bytesNeeded);
let randomValue = 0;
for (let i = 0; i < bytesNeeded; i++) {
randomValue |= bytes[i] << 8 * i;
}
randomValue = randomValue & mask;
if (randomValue <= range) {
return min + randomValue;
} else {
return getCryptoRandomInt(min, max);
}
}
export function getComposeTerminalName(endpoint : string, stack : string) {
return "compose-" + endpoint + "-" + stack;
}
export function getCombinedTerminalName(endpoint : string, stack : string) {
return "combined-" + endpoint + "-" + stack;
}
export function getContainerTerminalName(endpoint : string, container : string) {
return "container-" + endpoint + "-" + container;
}
export function getContainerExecTerminalName(endpoint : string, stackName : string, container : string, index : number) {
return "container-exec-" + endpoint + "-" + stackName + "-" + container + "-" + index;
}
export function copyYAMLComments(doc : Document, src : Document) {
doc.comment = src.comment;
doc.commentBefore = src.commentBefore;
if (doc && doc.contents && src && src.contents) {
// @ts-ignore
copyYAMLCommentsItems(doc.contents.items, src.contents.items);
}
}
/**
* Copy yaml comments from srcItems to items
* Attempts to preserve comments by matching content rather than just array indices
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function copyYAMLCommentsItems(items: any, srcItems: any) {
if (!items || !srcItems) {
return;
}
// First pass - try to match items by their content
for (let i = 0; i < items.length; i++) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const item: any = items[i];
// Try to find matching source item by content
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const srcIndex = srcItems.findIndex((srcItem: any) =>
JSON.stringify(srcItem.value) === JSON.stringify(item.value) &&
JSON.stringify(srcItem.key) === JSON.stringify(item.key)
);
if (srcIndex !== -1) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const srcItem: any = srcItems[srcIndex];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const nextSrcItem: any = srcItems[srcIndex + 1];
if (item.key && srcItem.key) {
item.key.comment = srcItem.key.comment;
item.key.commentBefore = srcItem.key.commentBefore;
}
if (srcItem.comment) {
item.comment = srcItem.comment;
}
// Handle comments between array items
if (nextSrcItem && nextSrcItem.commentBefore) {
if (items[i + 1]) {
items[i + 1].commentBefore = nextSrcItem.commentBefore;
}
}
// Handle trailing comments after array items
if (srcItem.value && srcItem.value.comment) {
if (item.value) {
item.value.comment = srcItem.value.comment;
}
}
if (item.value && srcItem.value) {
if (typeof item.value === "object" && typeof srcItem.value === "object") {
item.value.comment = srcItem.value.comment;
item.value.commentBefore = srcItem.value.commentBefore;
if (item.value.items && srcItem.value.items) {
copyYAMLCommentsItems(item.value.items, srcItem.value.items);
}
}
}
}
}
}
/**
* Possible Inputs:
* ports:
* - "3000"
* - "3000-3005"
* - "8000:8000"
* - "9090-9091:8080-8081"
* - "49100:22"
* - "8000-9000:80"
* - "127.0.0.1:8001:8001"
* - "127.0.0.1:5000-5010:5000-5010"
* - "0.0.0.0:8080->8080/tcp"
* - "6060:6060/udp"
* @param input
* @param hostname
*/
export function parseDockerPort(input : string, hostname : string) {
let port;
let display;
const parts = input.split("/");
let part1 = parts[0];
let protocol = parts[1] || "tcp";
// coming from docker ps, split host part
const arrow = part1.indexOf("->");
if (arrow >= 0) {
part1 = part1.split("->")[0];
const colon = part1.indexOf(":");
if (colon >= 0) {
part1 = part1.split(":")[1];
}
}
// Split the last ":"
const lastColon = part1.lastIndexOf(":");
if (lastColon === -1) {
// No colon, so it's just a port or port range
// Check if it's a port range
const dash = part1.indexOf("-");
if (dash === -1) {
// No dash, so it's just a port
port = part1;
} else {
// Has dash, so it's a port range, use the first port
port = part1.substring(0, dash);
}
display = part1;
} else {
// Has colon, so it's a port mapping
let hostPart = part1.substring(0, lastColon);
display = hostPart;
// Check if it's a port range
const dash = part1.indexOf("-");
if (dash !== -1) {
// Has dash, so it's a port range, use the first port
hostPart = part1.substring(0, dash);
}
// Check if it has a ip (ip:port)
const colon = hostPart.indexOf(":");
if (colon !== -1) {
// Has colon, so it's a ip:port
hostname = hostPart.substring(0, colon);
port = hostPart.substring(colon + 1);
} else {
// No colon, so it's just a port
port = hostPart;
}
}
let portInt = parseInt(port);
if (portInt == 443) {
protocol = "https";
} else if (protocol === "tcp") {
protocol = "http";
}
return {
url: protocol + "://" + hostname + ":" + portInt,
display: display,
};
}
export function envsubst(string : string, variables : LooseObject) : string {
return replaceVariablesSync(string, variables)[0];
}
/**
* Traverse all values in the yaml and for each value, if there are template variables, replace it environment variables
* Emulates the behavior of how docker-compose handles environment variables in yaml files
* @param content Yaml string
* @param env Environment variables
* @returns string Yaml string with environment variables replaced
*/
export function envsubstYAML(content : string, env : DotenvParseOutput) : string {
const doc = yaml.parseDocument(content);
if (doc.contents) {
// @ts-ignore
for (const item of doc.contents.items) {
traverseYAML(item, env);
}
}
return doc.toString();
}
/**
* Used for envsubstYAML(...)
* @param pair
* @param env
*/
function traverseYAML(pair : Pair, env : DotenvParseOutput) : void {
// @ts-ignore
if (pair.value && pair.value.items) {
// @ts-ignore
for (const item of pair.value.items) {
if (item instanceof Pair) {
traverseYAML(item, env);
} else if (item instanceof Scalar) {
let value = item.value as unknown;
if (typeof(value) === "string") {
item.value = envsubst(value, env);
}
}
}
// @ts-ignore
} else if (pair.value && typeof(pair.value.value) === "string") {
// @ts-ignore
pair.value.value = envsubst(pair.value.value, env);
}
}