mirror of
https://github.com/louislam/dockge.git
synced 2025-08-12 19:28:11 +02:00
Compare commits
1 Commits
fix-check-
...
sort
Author | SHA1 | Date | |
---|---|---|---|
79552b90a3 |
3
.github/DISCUSSION_TEMPLATE/ask-for-help.yml
vendored
3
.github/DISCUSSION_TEMPLATE/ask-for-help.yml
vendored
@ -1,4 +1,5 @@
|
|||||||
labels: [help]
|
title: "❓ Ask for help"
|
||||||
|
labels: [help]
|
||||||
body:
|
body:
|
||||||
- type: checkboxes
|
- type: checkboxes
|
||||||
id: no-duplicate-issues
|
id: no-duplicate-issues
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
title: 🚀 Feature Request
|
||||||
labels: [feature-request]
|
labels: [feature-request]
|
||||||
body:
|
body:
|
||||||
- type: checkboxes
|
- type: checkboxes
|
||||||
@ -51,4 +52,4 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
label: "📝 Additional Context"
|
label: "📝 Additional Context"
|
||||||
description: "Add any other context or screenshots about the feature request here."
|
description: "Add any other context or screenshots about the feature request here."
|
||||||
placeholder: "..."
|
placeholder: "..."
|
8
.github/ISSUE_TEMPLATE/ask-for-help.yaml
vendored
8
.github/ISSUE_TEMPLATE/ask-for-help.yaml
vendored
@ -1,14 +1,14 @@
|
|||||||
name: "⚠️ Ask for help (Please go to the \"Discussions\" tab to submit a Help Request)"
|
name: "❓ Ask for help"
|
||||||
description: "⚠️ Please go to the \"Discussions\" tab to submit a Help Request"
|
description: "Please go to the Discussions tab to submit a Help Request"
|
||||||
body:
|
body:
|
||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
value: |
|
value: |
|
||||||
⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ Please go to https://github.com/louislam/dockge/discussions/new?category=ask-for-help
|
Please go to https://github.com/louislam/dockge/discussions/new?category=ask-for-help
|
||||||
- type: checkboxes
|
- type: checkboxes
|
||||||
id: no-duplicate-issues
|
id: no-duplicate-issues
|
||||||
attributes:
|
attributes:
|
||||||
label: "Issues are for bug reports only, please go to the \"Discussions\" tab to submit a Feature Request"
|
label: "Issues are for bug reports only"
|
||||||
options:
|
options:
|
||||||
- label: "I understand"
|
- label: "I understand"
|
||||||
required: true
|
required: true
|
||||||
|
8
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
8
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
@ -1,14 +1,14 @@
|
|||||||
name: 🚀 Feature Request (Please go to the "Discussions" tab to submit a Feature Request)
|
name: 🚀 Feature Request
|
||||||
description: "⚠️ Please go to the \"Discussions\" tab to submit a Feature Request"
|
description: "Please go to the Discussions tab to submit a Feature Request"
|
||||||
body:
|
body:
|
||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
value: |
|
value: |
|
||||||
⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ Please go to https://github.com/louislam/dockge/discussions/new?category=ask-for-help
|
Please go to https://github.com/louislam/dockge/discussions/new?category=feature-request
|
||||||
- type: checkboxes
|
- type: checkboxes
|
||||||
id: no-duplicate-issues
|
id: no-duplicate-issues
|
||||||
attributes:
|
attributes:
|
||||||
label: "Issues are for bug reports only, please go to the \"Discussions\" tab to submit a Feature Request"
|
label: "Issues are for bug reports only"
|
||||||
options:
|
options:
|
||||||
- label: "I understand"
|
- label: "I understand"
|
||||||
required: true
|
required: true
|
||||||
|
@ -131,7 +131,7 @@ Be sure to read the [guide](https://github.com/louislam/dockge/blob/master/CONTR
|
|||||||
|
|
||||||
#### "Dockge"?
|
#### "Dockge"?
|
||||||
|
|
||||||
"Dockge" is a coinage word which is created by myself. I originally hoped it sounds like `Dodge`, but apparently many people called it `Dockage`, it is also acceptable.
|
"Dockge" is a coinage word which is created by myself. I hope it sounds like `Dodge`.
|
||||||
|
|
||||||
The naming idea came from Twitch emotes like `sadge`, `bedge` or `wokege`. They all end in `-ge`.
|
The naming idea came from Twitch emotes like `sadge`, `bedge` or `wokege`. They all end in `-ge`.
|
||||||
|
|
||||||
@ -148,13 +148,13 @@ Yes, you can. However, you need to move your compose file into the stacks direct
|
|||||||
3. In Dockge, click the " Scan Stacks Folder" button in the top-right corner's dropdown menu
|
3. In Dockge, click the " Scan Stacks Folder" button in the top-right corner's dropdown menu
|
||||||
4. Now you should see your stack in the list
|
4. Now you should see your stack in the list
|
||||||
|
|
||||||
#### Is Dockge a Portainer replacement?
|
#### Is Dockge a Portainer replcement?
|
||||||
|
|
||||||
Yes or no. Portainer provides a lot of Docker features. While Dockge is currently only focusing on docker-compose with a better user interface and better user experience.
|
Yes or no. Portainer provides a lot of Docker features. While Dockge is currently only focusing on docker-compose with a better user interface and better user experience.
|
||||||
|
|
||||||
If you want to manage your container with docker-compose only, the answer may be yes.
|
If you want to manage your container with docker-compose only, the answer may be yes.
|
||||||
|
|
||||||
If you still need to manage something like docker networks, single containers, the answer may be no.
|
If you still need to manage something like docker networks, signle containers, the answer may be no.
|
||||||
|
|
||||||
#### Can I install both Dockge and Portainer?
|
#### Can I install both Dockge and Portainer?
|
||||||
|
|
||||||
|
@ -3,55 +3,69 @@ import compareVersions from "compare-versions";
|
|||||||
import packageJSON from "../package.json";
|
import packageJSON from "../package.json";
|
||||||
import { Settings } from "./settings";
|
import { Settings } from "./settings";
|
||||||
|
|
||||||
|
export const obj = {
|
||||||
|
version: packageJSON.version,
|
||||||
|
latestVersion: null,
|
||||||
|
};
|
||||||
|
export default obj;
|
||||||
|
|
||||||
// How much time in ms to wait between update checks
|
// How much time in ms to wait between update checks
|
||||||
const UPDATE_CHECKER_INTERVAL_MS = 1000 * 60 * 60 * 48;
|
const UPDATE_CHECKER_INTERVAL_MS = 1000 * 60 * 60 * 48;
|
||||||
const CHECK_URL = "https://dockge.kuma.pet/version";
|
const CHECK_URL = "https://dockge.kuma.pet/version";
|
||||||
|
|
||||||
class CheckVersion {
|
let interval : NodeJS.Timeout;
|
||||||
version = packageJSON.version;
|
|
||||||
latestVersion? : string;
|
|
||||||
interval? : NodeJS.Timeout;
|
|
||||||
|
|
||||||
async startInterval() {
|
export function startInterval() {
|
||||||
const check = async () => {
|
const check = async () => {
|
||||||
if (await Settings.get("checkUpdate") === false) {
|
if (await Settings.get("checkUpdate") === false) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug("update-checker", "Retrieving latest versions");
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(CHECK_URL);
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
// For debug
|
||||||
|
if (process.env.TEST_CHECK_VERSION === "1") {
|
||||||
|
data.slow = "1000.0.0";
|
||||||
}
|
}
|
||||||
|
|
||||||
log.debug("update-checker", "Retrieving latest versions");
|
const checkBeta = await Settings.get("checkBeta");
|
||||||
|
|
||||||
try {
|
if (checkBeta && data.beta) {
|
||||||
const res = await fetch(CHECK_URL);
|
if (compareVersions.compare(data.beta, data.slow, ">")) {
|
||||||
const data = await res.json();
|
obj.latestVersion = data.beta;
|
||||||
|
return;
|
||||||
// For debug
|
|
||||||
if (process.env.TEST_CHECK_VERSION === "1") {
|
|
||||||
data.slow = "1000.0.0";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const checkBeta = await Settings.get("checkBeta");
|
|
||||||
|
|
||||||
if (checkBeta && data.beta) {
|
|
||||||
if (compareVersions.compare(data.beta, data.slow, ">")) {
|
|
||||||
this.latestVersion = data.beta;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.slow) {
|
|
||||||
this.latestVersion = data.slow;
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (_) {
|
|
||||||
log.info("update-checker", "Failed to check for new versions");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
if (data.slow) {
|
||||||
|
obj.latestVersion = data.slow;
|
||||||
|
}
|
||||||
|
|
||||||
await check();
|
} catch (_) {
|
||||||
this.interval = setInterval(check, UPDATE_CHECKER_INTERVAL_MS);
|
log.info("update-checker", "Failed to check for new versions");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
check();
|
||||||
|
interval = setInterval(check, UPDATE_CHECKER_INTERVAL_MS);
|
||||||
}
|
}
|
||||||
|
|
||||||
const checkVersion = new CheckVersion();
|
/**
|
||||||
export default checkVersion;
|
* Enable the check update feature
|
||||||
|
* @param value Should the check update feature be enabled?
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export async function enableCheckUpdate(value : boolean) {
|
||||||
|
await Settings.set("checkUpdate", value);
|
||||||
|
|
||||||
|
clearInterval(interval);
|
||||||
|
|
||||||
|
if (value) {
|
||||||
|
startInterval();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -29,11 +29,9 @@ import { Stack } from "./stack";
|
|||||||
import { Cron } from "croner";
|
import { Cron } from "croner";
|
||||||
import gracefulShutdown from "http-graceful-shutdown";
|
import gracefulShutdown from "http-graceful-shutdown";
|
||||||
import User from "./models/user";
|
import User from "./models/user";
|
||||||
import childProcessAsync from "promisify-child-process";
|
import childProcess from "child_process";
|
||||||
import { Terminal } from "./terminal";
|
import { Terminal } from "./terminal";
|
||||||
|
|
||||||
import "dotenv/config";
|
|
||||||
|
|
||||||
export class DockgeServer {
|
export class DockgeServer {
|
||||||
app : Express;
|
app : Express;
|
||||||
httpServer : http.Server;
|
httpServer : http.Server;
|
||||||
@ -308,7 +306,6 @@ export class DockgeServer {
|
|||||||
this.sendStackList(true);
|
this.sendStackList(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
checkVersion.startInterval();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
gracefulShutdown(this.httpServer, {
|
gracefulShutdown(this.httpServer, {
|
||||||
@ -486,7 +483,7 @@ export class DockgeServer {
|
|||||||
return jwtSecretBean;
|
return jwtSecretBean;
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendStackList(useCache = false) {
|
sendStackList(useCache = false) {
|
||||||
let roomList = this.io.sockets.adapter.rooms.keys();
|
let roomList = this.io.sockets.adapter.rooms.keys();
|
||||||
let map : Map<string, object> | undefined;
|
let map : Map<string, object> | undefined;
|
||||||
|
|
||||||
@ -497,7 +494,7 @@ export class DockgeServer {
|
|||||||
// Get the list only if there is a room
|
// Get the list only if there is a room
|
||||||
if (!map) {
|
if (!map) {
|
||||||
map = new Map();
|
map = new Map();
|
||||||
let stackList = await Stack.getStackList(this, useCache);
|
let stackList = Stack.getStackList(this, useCache);
|
||||||
|
|
||||||
for (let [ stackName, stack ] of stackList) {
|
for (let [ stackName, stack ] of stackList) {
|
||||||
map.set(stackName, stack.toSimpleJSON());
|
map.set(stackName, stack.toSimpleJSON());
|
||||||
@ -513,8 +510,8 @@ export class DockgeServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendStackStatusList() {
|
sendStackStatusList() {
|
||||||
let statusList = await Stack.getStatusList();
|
let statusList = Stack.getStatusList();
|
||||||
|
|
||||||
let roomList = this.io.sockets.adapter.rooms.keys();
|
let roomList = this.io.sockets.adapter.rooms.keys();
|
||||||
|
|
||||||
@ -532,15 +529,8 @@ export class DockgeServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getDockerNetworkList() : Promise<string[]> {
|
getDockerNetworkList() : string[] {
|
||||||
let res = await childProcessAsync.spawn("docker", [ "network", "ls", "--format", "{{.Name}}" ], {
|
let res = childProcess.spawnSync("docker", [ "network", "ls", "--format", "{{.Name}}" ]);
|
||||||
encoding: "utf-8",
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!res.stdout) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
let list = res.stdout.toString().split("\n");
|
let list = res.stdout.toString().split("\n");
|
||||||
|
|
||||||
// Remove empty string item
|
// Remove empty string item
|
||||||
|
@ -12,7 +12,7 @@ export class DockerSocketHandler extends SocketHandler {
|
|||||||
socket.on("deployStack", async (name : unknown, composeYAML : unknown, composeENV : unknown, isAdd : unknown, callback) => {
|
socket.on("deployStack", async (name : unknown, composeYAML : unknown, composeENV : unknown, isAdd : unknown, callback) => {
|
||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
const stack = await this.saveStack(socket, server, name, composeYAML, composeENV, isAdd);
|
const stack = this.saveStack(socket, server, name, composeYAML, composeENV, isAdd);
|
||||||
await stack.deploy(socket);
|
await stack.deploy(socket);
|
||||||
server.sendStackList();
|
server.sendStackList();
|
||||||
callback({
|
callback({
|
||||||
@ -45,7 +45,7 @@ export class DockerSocketHandler extends SocketHandler {
|
|||||||
if (typeof(name) !== "string") {
|
if (typeof(name) !== "string") {
|
||||||
throw new ValidationError("Name must be a string");
|
throw new ValidationError("Name must be a string");
|
||||||
}
|
}
|
||||||
const stack = await Stack.getStack(server, name);
|
const stack = Stack.getStack(server, name);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await stack.delete(socket);
|
await stack.delete(socket);
|
||||||
@ -65,7 +65,7 @@ export class DockerSocketHandler extends SocketHandler {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("getStack", async (stackName : unknown, callback) => {
|
socket.on("getStack", (stackName : unknown, callback) => {
|
||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
|
|
||||||
@ -73,7 +73,7 @@ export class DockerSocketHandler extends SocketHandler {
|
|||||||
throw new ValidationError("Stack name must be a string");
|
throw new ValidationError("Stack name must be a string");
|
||||||
}
|
}
|
||||||
|
|
||||||
const stack = await Stack.getStack(server, stackName);
|
const stack = Stack.getStack(server, stackName);
|
||||||
|
|
||||||
if (stack.isManagedByDockge) {
|
if (stack.isManagedByDockge) {
|
||||||
stack.joinCombinedTerminal(socket);
|
stack.joinCombinedTerminal(socket);
|
||||||
@ -111,7 +111,7 @@ export class DockerSocketHandler extends SocketHandler {
|
|||||||
throw new ValidationError("Stack name must be a string");
|
throw new ValidationError("Stack name must be a string");
|
||||||
}
|
}
|
||||||
|
|
||||||
const stack = await Stack.getStack(server, stackName);
|
const stack = Stack.getStack(server, stackName);
|
||||||
await stack.start(socket);
|
await stack.start(socket);
|
||||||
callback({
|
callback({
|
||||||
ok: true,
|
ok: true,
|
||||||
@ -135,7 +135,7 @@ export class DockerSocketHandler extends SocketHandler {
|
|||||||
throw new ValidationError("Stack name must be a string");
|
throw new ValidationError("Stack name must be a string");
|
||||||
}
|
}
|
||||||
|
|
||||||
const stack = await Stack.getStack(server, stackName);
|
const stack = Stack.getStack(server, stackName);
|
||||||
await stack.stop(socket);
|
await stack.stop(socket);
|
||||||
callback({
|
callback({
|
||||||
ok: true,
|
ok: true,
|
||||||
@ -156,7 +156,7 @@ export class DockerSocketHandler extends SocketHandler {
|
|||||||
throw new ValidationError("Stack name must be a string");
|
throw new ValidationError("Stack name must be a string");
|
||||||
}
|
}
|
||||||
|
|
||||||
const stack = await Stack.getStack(server, stackName);
|
const stack = Stack.getStack(server, stackName);
|
||||||
await stack.restart(socket);
|
await stack.restart(socket);
|
||||||
callback({
|
callback({
|
||||||
ok: true,
|
ok: true,
|
||||||
@ -177,7 +177,7 @@ export class DockerSocketHandler extends SocketHandler {
|
|||||||
throw new ValidationError("Stack name must be a string");
|
throw new ValidationError("Stack name must be a string");
|
||||||
}
|
}
|
||||||
|
|
||||||
const stack = await Stack.getStack(server, stackName);
|
const stack = Stack.getStack(server, stackName);
|
||||||
await stack.update(socket);
|
await stack.update(socket);
|
||||||
callback({
|
callback({
|
||||||
ok: true,
|
ok: true,
|
||||||
@ -198,7 +198,7 @@ export class DockerSocketHandler extends SocketHandler {
|
|||||||
throw new ValidationError("Stack name must be a string");
|
throw new ValidationError("Stack name must be a string");
|
||||||
}
|
}
|
||||||
|
|
||||||
const stack = await Stack.getStack(server, stackName);
|
const stack = Stack.getStack(server, stackName);
|
||||||
await stack.down(socket);
|
await stack.down(socket);
|
||||||
callback({
|
callback({
|
||||||
ok: true,
|
ok: true,
|
||||||
@ -219,7 +219,7 @@ export class DockerSocketHandler extends SocketHandler {
|
|||||||
throw new ValidationError("Stack name must be a string");
|
throw new ValidationError("Stack name must be a string");
|
||||||
}
|
}
|
||||||
|
|
||||||
const stack = await Stack.getStack(server, stackName, true);
|
const stack = Stack.getStack(server, stackName, true);
|
||||||
const serviceStatusList = Object.fromEntries(await stack.getServiceStatusList());
|
const serviceStatusList = Object.fromEntries(await stack.getServiceStatusList());
|
||||||
callback({
|
callback({
|
||||||
ok: true,
|
ok: true,
|
||||||
@ -234,7 +234,7 @@ export class DockerSocketHandler extends SocketHandler {
|
|||||||
socket.on("getDockerNetworkList", async (callback) => {
|
socket.on("getDockerNetworkList", async (callback) => {
|
||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
const dockerNetworkList = await server.getDockerNetworkList();
|
const dockerNetworkList = server.getDockerNetworkList();
|
||||||
callback({
|
callback({
|
||||||
ok: true,
|
ok: true,
|
||||||
dockerNetworkList,
|
dockerNetworkList,
|
||||||
@ -264,7 +264,7 @@ export class DockerSocketHandler extends SocketHandler {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveStack(socket : DockgeSocket, server : DockgeServer, name : unknown, composeYAML : unknown, composeENV : unknown, isAdd : unknown) : Promise<Stack> {
|
saveStack(socket : DockgeSocket, server : DockgeServer, name : unknown, composeYAML : unknown, composeENV : unknown, isAdd : unknown) : Stack {
|
||||||
// Check types
|
// Check types
|
||||||
if (typeof(name) !== "string") {
|
if (typeof(name) !== "string") {
|
||||||
throw new ValidationError("Name must be a string");
|
throw new ValidationError("Name must be a string");
|
||||||
@ -280,7 +280,7 @@ export class DockerSocketHandler extends SocketHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const stack = new Stack(server, name, composeYAML, composeENV, false);
|
const stack = new Stack(server, name, composeYAML, composeENV, false);
|
||||||
await stack.save(isAdd);
|
stack.save(isAdd);
|
||||||
return stack;
|
return stack;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,7 +101,7 @@ export class TerminalSocketHandler extends SocketHandler {
|
|||||||
log.debug("interactiveTerminal", "Service name: " + serviceName);
|
log.debug("interactiveTerminal", "Service name: " + serviceName);
|
||||||
|
|
||||||
// Get stack
|
// Get stack
|
||||||
const stack = await Stack.getStack(server, stackName);
|
const stack = Stack.getStack(server, stackName);
|
||||||
stack.joinContainerTerminal(socket, serviceName, shell);
|
stack.joinContainerTerminal(socket, serviceName, shell);
|
||||||
|
|
||||||
callback({
|
callback({
|
||||||
@ -151,7 +151,7 @@ export class TerminalSocketHandler extends SocketHandler {
|
|||||||
throw new ValidationError("Stack name must be a string.");
|
throw new ValidationError("Stack name must be a string.");
|
||||||
}
|
}
|
||||||
|
|
||||||
const stack = await Stack.getStack(server, stackName);
|
const stack = Stack.getStack(server, stackName);
|
||||||
await stack.leaveCombinedTerminal(socket);
|
await stack.leaveCombinedTerminal(socket);
|
||||||
|
|
||||||
callback({
|
callback({
|
||||||
|
128
backend/stack.ts
128
backend/stack.ts
@ -1,8 +1,8 @@
|
|||||||
import { DockgeServer } from "./dockge-server";
|
import { DockgeServer } from "./dockge-server";
|
||||||
import fs, { promises as fsAsync } from "fs";
|
import fs from "fs";
|
||||||
import { log } from "./log";
|
import { log } from "./log";
|
||||||
import yaml from "yaml";
|
import yaml from "yaml";
|
||||||
import { DockgeSocket, fileExists, ValidationError } from "./util-server";
|
import { DockgeSocket, ValidationError } from "./util-server";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import {
|
import {
|
||||||
COMBINED_TERMINAL_COLS,
|
COMBINED_TERMINAL_COLS,
|
||||||
@ -16,7 +16,7 @@ import {
|
|||||||
UNKNOWN
|
UNKNOWN
|
||||||
} from "./util-common";
|
} from "./util-common";
|
||||||
import { InteractiveTerminal, Terminal } from "./terminal";
|
import { InteractiveTerminal, Terminal } from "./terminal";
|
||||||
import childProcessAsync from "promisify-child-process";
|
import childProcess from "child_process";
|
||||||
|
|
||||||
export class Stack {
|
export class Stack {
|
||||||
|
|
||||||
@ -72,15 +72,11 @@ export class Stack {
|
|||||||
/**
|
/**
|
||||||
* Get the status of the stack from `docker compose ps --format json`
|
* Get the status of the stack from `docker compose ps --format json`
|
||||||
*/
|
*/
|
||||||
async ps() : Promise<object> {
|
ps() : object {
|
||||||
let res = await childProcessAsync.spawn("docker", [ "compose", "ps", "--format", "json" ], {
|
let res = childProcess.execSync("docker compose ps --format json", {
|
||||||
cwd: this.path,
|
cwd: this.path
|
||||||
encoding: "utf-8",
|
|
||||||
});
|
});
|
||||||
if (!res.stdout) {
|
return JSON.parse(res.toString());
|
||||||
return {};
|
|
||||||
}
|
|
||||||
return JSON.parse(res.stdout.toString());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get isManagedByDockge() : boolean {
|
get isManagedByDockge() : boolean {
|
||||||
@ -99,15 +95,6 @@ export class Stack {
|
|||||||
|
|
||||||
// Check YAML format
|
// Check YAML format
|
||||||
yaml.parse(this.composeYAML);
|
yaml.parse(this.composeYAML);
|
||||||
|
|
||||||
let lines = this.composeENV.split("\n");
|
|
||||||
|
|
||||||
// Check if the .env is able to pass docker-compose
|
|
||||||
// Prevent "setenv: The parameter is incorrect"
|
|
||||||
// It only happens when there is one line and it doesn't contain "="
|
|
||||||
if (lines.length === 1 && !lines[0].includes("=") && lines[0].length > 0) {
|
|
||||||
throw new ValidationError("Invalid .env format");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get composeYAML() : string {
|
get composeYAML() : string {
|
||||||
@ -155,35 +142,29 @@ export class Stack {
|
|||||||
* Save the stack to the disk
|
* Save the stack to the disk
|
||||||
* @param isAdd
|
* @param isAdd
|
||||||
*/
|
*/
|
||||||
async save(isAdd : boolean) {
|
save(isAdd : boolean) {
|
||||||
this.validate();
|
this.validate();
|
||||||
|
|
||||||
let dir = this.path;
|
let dir = this.path;
|
||||||
|
|
||||||
// Check if the name is used if isAdd
|
// Check if the name is used if isAdd
|
||||||
if (isAdd) {
|
if (isAdd) {
|
||||||
if (await fileExists(dir)) {
|
if (fs.existsSync(dir)) {
|
||||||
throw new ValidationError("Stack name already exists");
|
throw new ValidationError("Stack name already exists");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the stack folder
|
// Create the stack folder
|
||||||
await fsAsync.mkdir(dir);
|
fs.mkdirSync(dir);
|
||||||
} else {
|
} else {
|
||||||
if (!await fileExists(dir)) {
|
if (!fs.existsSync(dir)) {
|
||||||
throw new ValidationError("Stack not found");
|
throw new ValidationError("Stack not found");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write or overwrite the compose.yaml
|
// Write or overwrite the compose.yaml
|
||||||
await fsAsync.writeFile(path.join(dir, this._composeFileName), this.composeYAML);
|
fs.writeFileSync(path.join(dir, this._composeFileName), this.composeYAML);
|
||||||
|
|
||||||
const envPath = path.join(dir, ".env");
|
|
||||||
|
|
||||||
// Write or overwrite the .env
|
// Write or overwrite the .env
|
||||||
// If .env is not existing and the composeENV is empty, we don't need to write it
|
fs.writeFileSync(path.join(dir, ".env"), this.composeENV);
|
||||||
if (await fileExists(envPath) || this.composeENV.trim() !== "") {
|
|
||||||
await fsAsync.writeFile(envPath, this.composeENV);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async deploy(socket? : DockgeSocket) : Promise<number> {
|
async deploy(socket? : DockgeSocket) : Promise<number> {
|
||||||
@ -203,7 +184,7 @@ export class Stack {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Remove the stack folder
|
// Remove the stack folder
|
||||||
await fsAsync.rm(this.path, {
|
fs.rmSync(this.path, {
|
||||||
recursive: true,
|
recursive: true,
|
||||||
force: true
|
force: true
|
||||||
});
|
});
|
||||||
@ -211,8 +192,8 @@ export class Stack {
|
|||||||
return exitCode;
|
return exitCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateStatus() {
|
updateStatus() {
|
||||||
let statusList = await Stack.getStatusList();
|
let statusList = Stack.getStatusList();
|
||||||
let status = statusList.get(this.name);
|
let status = statusList.get(this.name);
|
||||||
|
|
||||||
if (status) {
|
if (status) {
|
||||||
@ -222,7 +203,7 @@ export class Stack {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static async getStackList(server : DockgeServer, useCacheForManaged = false) : Promise<Map<string, Stack>> {
|
static getStackList(server : DockgeServer, useCacheForManaged = false) : Map<string, Stack> {
|
||||||
let stacksDir = server.stacksDir;
|
let stacksDir = server.stacksDir;
|
||||||
let stackList : Map<string, Stack>;
|
let stackList : Map<string, Stack>;
|
||||||
|
|
||||||
@ -233,16 +214,16 @@ export class Stack {
|
|||||||
stackList = new Map<string, Stack>();
|
stackList = new Map<string, Stack>();
|
||||||
|
|
||||||
// Scan the stacks directory, and get the stack list
|
// Scan the stacks directory, and get the stack list
|
||||||
let filenameList = await fsAsync.readdir(stacksDir);
|
let filenameList = fs.readdirSync(stacksDir);
|
||||||
|
|
||||||
for (let filename of filenameList) {
|
for (let filename of filenameList) {
|
||||||
try {
|
try {
|
||||||
// Check if it is a directory
|
// Check if it is a directory
|
||||||
let stat = await fsAsync.stat(path.join(stacksDir, filename));
|
let stat = fs.statSync(path.join(stacksDir, filename));
|
||||||
if (!stat.isDirectory()) {
|
if (!stat.isDirectory()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let stack = await this.getStack(server, filename);
|
let stack = this.getStack(server, filename);
|
||||||
stack._status = CREATED_FILE;
|
stack._status = CREATED_FILE;
|
||||||
stackList.set(filename, stack);
|
stackList.set(filename, stack);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -257,15 +238,8 @@ export class Stack {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get status from docker compose ls
|
// Get status from docker compose ls
|
||||||
let res = await childProcessAsync.spawn("docker", [ "compose", "ls", "--all", "--format", "json" ], {
|
let res = childProcess.execSync("docker compose ls --all --format json");
|
||||||
encoding: "utf-8",
|
let composeList = JSON.parse(res.toString());
|
||||||
});
|
|
||||||
|
|
||||||
if (!res.stdout) {
|
|
||||||
return stackList;
|
|
||||||
}
|
|
||||||
|
|
||||||
let composeList = JSON.parse(res.stdout.toString());
|
|
||||||
|
|
||||||
for (let composeStack of composeList) {
|
for (let composeStack of composeList) {
|
||||||
let stack = stackList.get(composeStack.Name);
|
let stack = stackList.get(composeStack.Name);
|
||||||
@ -291,18 +265,11 @@ export class Stack {
|
|||||||
* Get the status list, it will be used to update the status of the stacks
|
* Get the status list, it will be used to update the status of the stacks
|
||||||
* Not all status will be returned, only the stack that is deployed or created to `docker compose` will be returned
|
* Not all status will be returned, only the stack that is deployed or created to `docker compose` will be returned
|
||||||
*/
|
*/
|
||||||
static async getStatusList() : Promise<Map<string, number>> {
|
static getStatusList() : Map<string, number> {
|
||||||
let statusList = new Map<string, number>();
|
let statusList = new Map<string, number>();
|
||||||
|
|
||||||
let res = await childProcessAsync.spawn("docker", [ "compose", "ls", "--all", "--format", "json" ], {
|
let res = childProcess.execSync("docker compose ls --all --format json");
|
||||||
encoding: "utf-8",
|
let composeList = JSON.parse(res.toString());
|
||||||
});
|
|
||||||
|
|
||||||
if (!res.stdout) {
|
|
||||||
return statusList;
|
|
||||||
}
|
|
||||||
|
|
||||||
let composeList = JSON.parse(res.stdout.toString());
|
|
||||||
|
|
||||||
for (let composeStack of composeList) {
|
for (let composeStack of composeList) {
|
||||||
statusList.set(composeStack.Name, this.statusConvert(composeStack.Status));
|
statusList.set(composeStack.Name, this.statusConvert(composeStack.Status));
|
||||||
@ -330,13 +297,13 @@ export class Stack {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static async getStack(server: DockgeServer, stackName: string, skipFSOperations = false) : Promise<Stack> {
|
static getStack(server: DockgeServer, stackName: string, skipFSOperations = false) : Stack {
|
||||||
let dir = path.join(server.stacksDir, stackName);
|
let dir = path.join(server.stacksDir, stackName);
|
||||||
|
|
||||||
if (!skipFSOperations) {
|
if (!skipFSOperations) {
|
||||||
if (!await fileExists(dir) || !(await fsAsync.stat(dir)).isDirectory()) {
|
if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory()) {
|
||||||
// Maybe it is a stack managed by docker compose directly
|
// Maybe it is a stack managed by docker compose directly
|
||||||
let stackList = await this.getStackList(server, true);
|
let stackList = this.getStackList(server, true);
|
||||||
let stack = stackList.get(stackName);
|
let stack = stackList.get(stackName);
|
||||||
|
|
||||||
if (stack) {
|
if (stack) {
|
||||||
@ -407,7 +374,7 @@ export class Stack {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If the stack is not running, we don't need to restart it
|
// If the stack is not running, we don't need to restart it
|
||||||
await this.updateStatus();
|
this.updateStatus();
|
||||||
log.debug("update", "Status: " + this.status);
|
log.debug("update", "Status: " + this.status);
|
||||||
if (this.status !== RUNNING) {
|
if (this.status !== RUNNING) {
|
||||||
return exitCode;
|
return exitCode;
|
||||||
@ -455,35 +422,24 @@ export class Stack {
|
|||||||
async getServiceStatusList() {
|
async getServiceStatusList() {
|
||||||
let statusList = new Map<string, number>();
|
let statusList = new Map<string, number>();
|
||||||
|
|
||||||
try {
|
let res = childProcess.spawnSync("docker", [ "compose", "ps", "--format", "json" ], {
|
||||||
let res = await childProcessAsync.spawn("docker", [ "compose", "ps", "--format", "json" ], {
|
cwd: this.path,
|
||||||
cwd: this.path,
|
});
|
||||||
encoding: "utf-8",
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!res.stdout) {
|
let lines = res.stdout.toString().split("\n");
|
||||||
return statusList;
|
|
||||||
}
|
|
||||||
|
|
||||||
let lines = res.stdout?.toString().split("\n");
|
for (let line of lines) {
|
||||||
|
try {
|
||||||
for (let line of lines) {
|
let obj = JSON.parse(line);
|
||||||
try {
|
if (obj.Health === "") {
|
||||||
let obj = JSON.parse(line);
|
statusList.set(obj.Service, obj.State);
|
||||||
if (obj.Health === "") {
|
} else {
|
||||||
statusList.set(obj.Service, obj.State);
|
statusList.set(obj.Service, obj.Health);
|
||||||
} else {
|
|
||||||
statusList.set(obj.Service, obj.Health);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return statusList;
|
|
||||||
} catch (e) {
|
|
||||||
log.error("getServiceStatusList", e);
|
|
||||||
return statusList;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return statusList;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,6 @@ import { log } from "./log";
|
|||||||
import { ERROR_TYPE_VALIDATION } from "./util-common";
|
import { ERROR_TYPE_VALIDATION } from "./util-common";
|
||||||
import { R } from "redbean-node";
|
import { R } from "redbean-node";
|
||||||
import { verifyPassword } from "./password-hash";
|
import { verifyPassword } from "./password-hash";
|
||||||
import fs from "fs";
|
|
||||||
|
|
||||||
export interface JWTDecoded {
|
export interface JWTDecoded {
|
||||||
username : string;
|
username : string;
|
||||||
@ -83,9 +82,3 @@ export async function doubleCheckPassword(socket : DockgeSocket, currentPassword
|
|||||||
|
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fileExists(file : string) {
|
|
||||||
return fs.promises.access(file, fs.constants.F_OK)
|
|
||||||
.then(() => true)
|
|
||||||
.catch(() => false);
|
|
||||||
}
|
|
||||||
|
@ -39,7 +39,7 @@ for (let lang in languageList) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const rtlLangs = [ "fa", "ar-SY", "ur", "ar" ];
|
const rtlLangs = [ "fa", "ar-SY", "ur" ];
|
||||||
|
|
||||||
export const currentLocale = () => localStorage.locale
|
export const currentLocale = () => localStorage.locale
|
||||||
|| languageList[navigator.language] && navigator.language
|
|| languageList[navigator.language] && navigator.language
|
||||||
|
@ -98,6 +98,5 @@
|
|||||||
"reconnecting...": "Reconnecting…",
|
"reconnecting...": "Reconnecting…",
|
||||||
"connecting...": "Connecting to the socket server…",
|
"connecting...": "Connecting to the socket server…",
|
||||||
"url": "URL | URLs",
|
"url": "URL | URLs",
|
||||||
"extra": "Extra",
|
"extra": "Extra"
|
||||||
"newUpdate": "New Update"
|
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
"registry": "Registro",
|
"registry": "Registro",
|
||||||
"compose": "Compose",
|
"compose": "Compose",
|
||||||
"addFirstStackMsg": "Componi il tuo primo stack!",
|
"addFirstStackMsg": "Componi il tuo primo stack!",
|
||||||
"stackName": "Nome dello stack",
|
"stackName" : "Nome dello stack",
|
||||||
"deployStack": "Deploy",
|
"deployStack": "Deploy",
|
||||||
"deleteStack": "Cancella",
|
"deleteStack": "Cancella",
|
||||||
"stopStack": "Stop",
|
"stopStack": "Stop",
|
||||||
@ -23,7 +23,7 @@
|
|||||||
"editStack": "Modifica",
|
"editStack": "Modifica",
|
||||||
"discardStack": "Annulla",
|
"discardStack": "Annulla",
|
||||||
"saveStackDraft": "Salva",
|
"saveStackDraft": "Salva",
|
||||||
"notAvailableShort": "N/D",
|
"notAvailableShort" : "N/D",
|
||||||
"deleteStackMsg": "Sei sicuro di voler eliminare questo stack?",
|
"deleteStackMsg": "Sei sicuro di voler eliminare questo stack?",
|
||||||
"stackNotManagedByDockgeMsg": "Questo stack non è gestito da Dockge.",
|
"stackNotManagedByDockgeMsg": "Questo stack non è gestito da Dockge.",
|
||||||
"primaryHostname": "Hostname primario",
|
"primaryHostname": "Hostname primario",
|
||||||
@ -91,11 +91,5 @@
|
|||||||
"Allowed commands:": "Comandi permessi:",
|
"Allowed commands:": "Comandi permessi:",
|
||||||
"Internal Networks": "Reti interne",
|
"Internal Networks": "Reti interne",
|
||||||
"External Networks": "Reti esterne",
|
"External Networks": "Reti esterne",
|
||||||
"No External Networks": "Nessuna rete esterna",
|
"No External Networks": "Nessuna rete esterna"
|
||||||
"reverseProxyMsg1": "Utilizzando un proxy inverso?",
|
|
||||||
"reverseProxyMsg2": "Controlla come configurarlo per WebSocket",
|
|
||||||
"Cannot connect to the socket server.": "Impossibile connettersi al server socket.",
|
|
||||||
"connecting...": "Connessione al server socket…",
|
|
||||||
"extra": "Extra",
|
|
||||||
"reconnecting...": "Riconnessione…"
|
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
"editStack": "編集",
|
"editStack": "編集",
|
||||||
"discardStack": "破棄",
|
"discardStack": "破棄",
|
||||||
"saveStackDraft": "保存",
|
"saveStackDraft": "保存",
|
||||||
"stackNotManagedByDockgeMsg": "このスタックはDockgeによって管理されていません。",
|
"stackNotManagedByDockgeMsg": "このスタックはDockageによって管理されていません。",
|
||||||
"general": "一般",
|
"general": "一般",
|
||||||
"scanFolder": "スタックフォルダをスキャン",
|
"scanFolder": "スタックフォルダをスキャン",
|
||||||
"dockerImage": "イメージ",
|
"dockerImage": "イメージ",
|
||||||
|
@ -90,13 +90,5 @@
|
|||||||
"Allowed commands:": "허용된 명령어:",
|
"Allowed commands:": "허용된 명령어:",
|
||||||
"Internal Networks": "내부 네트워크",
|
"Internal Networks": "내부 네트워크",
|
||||||
"External Networks": "외부 네트워크",
|
"External Networks": "외부 네트워크",
|
||||||
"No External Networks": "외부 네트워크 없음",
|
"No External Networks": "외부 네트워크 없음"
|
||||||
"reverseProxyMsg2": "여기서 WebSocket을 위한 설정을 확인해 보세요",
|
|
||||||
"downStack": "정지 & Down",
|
|
||||||
"reverseProxyMsg1": "리버스 프록시를 사용하고 계신가요?",
|
|
||||||
"Cannot connect to the socket server.": "소켓 서버에 연결하지 못했습니다.",
|
|
||||||
"connecting...": "소켓 서버에 연결하는 중…",
|
|
||||||
"extra": "기타",
|
|
||||||
"url": "URL | URL",
|
|
||||||
"reconnecting...": "재연결 중…"
|
|
||||||
}
|
}
|
||||||
|
@ -16,8 +16,8 @@
|
|||||||
<span class="fs-4 title">Dockge</span>
|
<span class="fs-4 title">Dockge</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|
||||||
<a v-if="hasNewVersion" target="_blank" href="https://github.com/louislam/dockge/releases" class="btn btn-warning me-3">
|
<a v-if="hasNewVersion" target="_blank" href="https://github.com/louislam/dockge/releases" class="btn btn-info me-3">
|
||||||
<font-awesome-icon icon="arrow-alt-circle-up" /> {{ $t("newUpdate") }}
|
<font-awesome-icon icon="arrow-alt-circle-up" /> {{ $t("New Update") }}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<ul class="nav nav-pills">
|
<ul class="nav nav-pills">
|
||||||
|
@ -622,7 +622,7 @@ export default {
|
|||||||
greedy: true
|
greedy: true
|
||||||
},
|
},
|
||||||
"keyword": {
|
"keyword": {
|
||||||
pattern: /^\w*(?=[:=])/m,
|
pattern: /^[^ :=]*(?=[:=])/m,
|
||||||
greedy: true
|
greedy: true
|
||||||
},
|
},
|
||||||
"value": {
|
"value": {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "dockge",
|
"name": "dockge",
|
||||||
"version": "1.3.2",
|
"version": "1.2.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 18.0.0 && <= 18.17.1"
|
"node": ">= 18.0.0 && <= 18.17.1"
|
||||||
@ -33,7 +33,6 @@
|
|||||||
"composerize": "~1.4.1",
|
"composerize": "~1.4.1",
|
||||||
"croner": "~7.0.5",
|
"croner": "~7.0.5",
|
||||||
"dayjs": "~1.11.10",
|
"dayjs": "~1.11.10",
|
||||||
"dotenv": "~16.3.1",
|
|
||||||
"express": "~4.18.2",
|
"express": "~4.18.2",
|
||||||
"express-static-gzip": "~2.1.7",
|
"express-static-gzip": "~2.1.7",
|
||||||
"http-graceful-shutdown": "~3.1.13",
|
"http-graceful-shutdown": "~3.1.13",
|
||||||
@ -42,7 +41,6 @@
|
|||||||
"knex": "~2.5.1",
|
"knex": "~2.5.1",
|
||||||
"limiter-es6-compat": "~2.1.2",
|
"limiter-es6-compat": "~2.1.2",
|
||||||
"mysql2": "~3.6.3",
|
"mysql2": "~3.6.3",
|
||||||
"promisify-child-process": "~4.1.2",
|
|
||||||
"redbean-node": "~0.3.3",
|
"redbean-node": "~0.3.3",
|
||||||
"socket.io": "~4.7.2",
|
"socket.io": "~4.7.2",
|
||||||
"socket.io-client": "~4.7.2",
|
"socket.io-client": "~4.7.2",
|
||||||
|
16
pnpm-lock.yaml
generated
16
pnpm-lock.yaml
generated
@ -32,9 +32,6 @@ dependencies:
|
|||||||
dayjs:
|
dayjs:
|
||||||
specifier: ~1.11.10
|
specifier: ~1.11.10
|
||||||
version: 1.11.10
|
version: 1.11.10
|
||||||
dotenv:
|
|
||||||
specifier: ~16.3.1
|
|
||||||
version: 16.3.1
|
|
||||||
express:
|
express:
|
||||||
specifier: ~4.18.2
|
specifier: ~4.18.2
|
||||||
version: 4.18.2
|
version: 4.18.2
|
||||||
@ -59,9 +56,6 @@ dependencies:
|
|||||||
mysql2:
|
mysql2:
|
||||||
specifier: ~3.6.3
|
specifier: ~3.6.3
|
||||||
version: 3.6.3
|
version: 3.6.3
|
||||||
promisify-child-process:
|
|
||||||
specifier: ~4.1.2
|
|
||||||
version: 4.1.2
|
|
||||||
redbean-node:
|
redbean-node:
|
||||||
specifier: ~0.3.3
|
specifier: ~0.3.3
|
||||||
version: 0.3.3(mysql2@3.6.3)
|
version: 0.3.3(mysql2@3.6.3)
|
||||||
@ -2157,11 +2151,6 @@ packages:
|
|||||||
esutils: 2.0.3
|
esutils: 2.0.3
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/dotenv@16.3.1:
|
|
||||||
resolution: {integrity: sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/eastasianwidth@0.2.0:
|
/eastasianwidth@0.2.0:
|
||||||
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
|
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
|
||||||
dev: false
|
dev: false
|
||||||
@ -3898,11 +3887,6 @@ packages:
|
|||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/promisify-child-process@4.1.2:
|
|
||||||
resolution: {integrity: sha512-APnkIgmaHNJpkAn7k+CrJSi9WMuff5ctYFbD0CO2XIPkM8yO7d/ShouU2clywbpHV/DUsyc4bpJCsNgddNtx4g==}
|
|
||||||
engines: {node: '>=8'}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/proxy-addr@2.0.7:
|
/proxy-addr@2.0.7:
|
||||||
resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
|
resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
|
||||||
engines: {node: '>= 0.10'}
|
engines: {node: '>= 0.10'}
|
||||||
|
Reference in New Issue
Block a user