mirror of
https://github.com/louislam/dockge.git
synced 2024-12-01 06:34:03 +00:00
wip
This commit is contained in:
parent
314630724b
commit
2ed739b1b9
12 changed files with 114 additions and 104 deletions
|
@ -20,7 +20,7 @@ A fancy, easy-to-use and reactive docker stack (`docker-compose.yml`) manager.
|
|||
|
||||
## Motivations
|
||||
|
||||
- I have been using Portainer for some time, but I am sometimes not satisfied with it. For example, sometimes when I deploy a stack, the loading icon keeps spinning for a few minutes without progress. And sometimes error messages are not clear.
|
||||
- I have been using Portainer for some time, but for the stack management, I am sometimes not satisfied with it. For example, sometimes when I try to deploy a stack, the loading icon keeps spinning for a few minutes without progress. And sometimes error messages are not clear.
|
||||
- Try to develop with ES Module + TypeScript (Originally, I planned to use Deno or Bun.js, but they do not support for arm64, so I stepped back to Node.js)
|
||||
|
||||
|
||||
|
@ -34,8 +34,6 @@ If you love this project, please consider giving this project a ⭐.
|
|||
- Get app icons
|
||||
- Switch Docker context
|
||||
- Support Dockerfile and build
|
||||
- Zero-config private docker registry
|
||||
- Support Docker swarm
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -71,7 +71,7 @@ export class DockerSocketHandler extends SocketHandler {
|
|||
|
||||
const stack = Stack.getStack(server, stackName);
|
||||
|
||||
stack.startCombinedTerminal(socket);
|
||||
stack.joinCombinedTerminal(socket);
|
||||
|
||||
callback({
|
||||
ok: true,
|
||||
|
@ -113,7 +113,7 @@ export class DockerSocketHandler extends SocketHandler {
|
|||
});
|
||||
server.sendStackList();
|
||||
|
||||
stack.startCombinedTerminal(socket);
|
||||
stack.joinCombinedTerminal(socket);
|
||||
|
||||
} catch (e) {
|
||||
callbackError(e, callback);
|
||||
|
|
|
@ -13,6 +13,7 @@ import {
|
|||
PROGRESS_TERMINAL_ROWS
|
||||
} from "../util-common";
|
||||
import { InteractiveTerminal, MainTerminal, Terminal } from "../terminal";
|
||||
import { Stack } from "../stack";
|
||||
|
||||
export class TerminalSocketHandler extends SocketHandler {
|
||||
create(socket : DockgeSocket, server : DockgeServer) {
|
||||
|
@ -31,6 +32,7 @@ export class TerminalSocketHandler extends SocketHandler {
|
|||
|
||||
let terminal = Terminal.getTerminal(terminalName);
|
||||
if (terminal instanceof InteractiveTerminal) {
|
||||
log.debug("terminalInput", "Terminal found, writing to terminal.");
|
||||
terminal.write(cmd);
|
||||
} else {
|
||||
throw new Error("Terminal not found or it is not a Interactive Terminal.");
|
||||
|
@ -89,17 +91,12 @@ export class TerminalSocketHandler extends SocketHandler {
|
|||
throw new ValidationError("Service name must be a string.");
|
||||
}
|
||||
|
||||
const terminalName = getContainerExecTerminalName(stackName, serviceName, 0);
|
||||
let terminal = Terminal.getTerminal(terminalName);
|
||||
log.debug("interactiveTerminal", "Stack name: " + stackName);
|
||||
log.debug("interactiveTerminal", "Service name: " + serviceName);
|
||||
|
||||
if (!terminal) {
|
||||
terminal = new InteractiveTerminal(server, terminalName);
|
||||
terminal.rows = 50;
|
||||
log.debug("deployStack", "Terminal created");
|
||||
}
|
||||
|
||||
terminal.join(socket);
|
||||
terminal.start();
|
||||
// Get stack
|
||||
const stack = Stack.getStack(server, stackName);
|
||||
stack.joinContainerTerminal(socket, serviceName);
|
||||
|
||||
callback({
|
||||
ok: true,
|
||||
|
|
|
@ -10,12 +10,12 @@ import {
|
|||
CREATED_FILE,
|
||||
CREATED_STACK,
|
||||
EXITED, getCombinedTerminalName,
|
||||
getComposeTerminalName,
|
||||
getComposeTerminalName, getContainerExecTerminalName,
|
||||
PROGRESS_TERMINAL_ROWS,
|
||||
RUNNING,
|
||||
RUNNING, TERMINAL_ROWS,
|
||||
UNKNOWN
|
||||
} from "./util-common";
|
||||
import { Terminal } from "./terminal";
|
||||
import { InteractiveTerminal, Terminal } from "./terminal";
|
||||
import childProcess from "child_process";
|
||||
|
||||
export class Stack {
|
||||
|
@ -144,7 +144,7 @@ export class Stack {
|
|||
|
||||
async deploy(socket? : DockgeSocket) : Promise<number> {
|
||||
const terminalName = getComposeTerminalName(this.name);
|
||||
let exitCode = await Terminal.exec(this.server, socket, terminalName, "docker-compose", [ "up", "-d", "--remove-orphans" ], this.path);
|
||||
let exitCode = await Terminal.exec(this.server, socket, terminalName, "docker", [ "compose", "up", "-d", "--remove-orphans" ], this.path);
|
||||
if (exitCode !== 0) {
|
||||
throw new Error("Failed to deploy, please check the terminal output for more information.");
|
||||
}
|
||||
|
@ -153,7 +153,7 @@ export class Stack {
|
|||
|
||||
async delete(socket?: DockgeSocket) : Promise<number> {
|
||||
const terminalName = getComposeTerminalName(this.name);
|
||||
let exitCode = await Terminal.exec(this.server, socket, terminalName, "docker-compose", [ "down", "--remove-orphans", "--rmi", "all" ], this.path);
|
||||
let exitCode = await Terminal.exec(this.server, socket, terminalName, "docker", [ "compose", "down", "--remove-orphans", "--rmi", "all" ], this.path);
|
||||
if (exitCode !== 0) {
|
||||
throw new Error("Failed to delete, please check the terminal output for more information.");
|
||||
}
|
||||
|
@ -270,7 +270,7 @@ export class Stack {
|
|||
|
||||
async start(socket: DockgeSocket) {
|
||||
const terminalName = getComposeTerminalName(this.name);
|
||||
let exitCode = await Terminal.exec(this.server, socket, terminalName, "docker-compose", [ "up", "-d", "--remove-orphans" ], this.path);
|
||||
let exitCode = await Terminal.exec(this.server, socket, terminalName, "docker", [ "compose", "up", "-d", "--remove-orphans" ], this.path);
|
||||
if (exitCode !== 0) {
|
||||
throw new Error("Failed to start, please check the terminal output for more information.");
|
||||
}
|
||||
|
@ -279,7 +279,7 @@ export class Stack {
|
|||
|
||||
async stop(socket: DockgeSocket) : Promise<number> {
|
||||
const terminalName = getComposeTerminalName(this.name);
|
||||
let exitCode = await Terminal.exec(this.server, socket, terminalName, "docker-compose", [ "stop" ], this.path);
|
||||
let exitCode = await Terminal.exec(this.server, socket, terminalName, "docker", [ "compose", "stop" ], this.path);
|
||||
if (exitCode !== 0) {
|
||||
throw new Error("Failed to stop, please check the terminal output for more information.");
|
||||
}
|
||||
|
@ -288,7 +288,7 @@ export class Stack {
|
|||
|
||||
async restart(socket: DockgeSocket) : Promise<number> {
|
||||
const terminalName = getComposeTerminalName(this.name);
|
||||
let exitCode = await Terminal.exec(this.server, socket, terminalName, "docker-compose", [ "restart" ], this.path);
|
||||
let exitCode = await Terminal.exec(this.server, socket, terminalName, "docker", [ "compose", "restart" ], this.path);
|
||||
if (exitCode !== 0) {
|
||||
throw new Error("Failed to restart, please check the terminal output for more information.");
|
||||
}
|
||||
|
@ -297,23 +297,37 @@ export class Stack {
|
|||
|
||||
async update(socket: DockgeSocket) {
|
||||
const terminalName = getComposeTerminalName(this.name);
|
||||
let exitCode = await Terminal.exec(this.server, socket, terminalName, "docker-compose", [ "pull" ], this.path);
|
||||
let exitCode = await Terminal.exec(this.server, socket, terminalName, "docker", [ "compose", "pull" ], this.path);
|
||||
if (exitCode !== 0) {
|
||||
throw new Error("Failed to pull, please check the terminal output for more information.");
|
||||
}
|
||||
exitCode = await Terminal.exec(this.server, socket, terminalName, "docker-compose", [ "up", "-d", "--remove-orphans" ], this.path);
|
||||
exitCode = await Terminal.exec(this.server, socket, terminalName, "docker", [ "compose", "up", "-d", "--remove-orphans" ], this.path);
|
||||
if (exitCode !== 0) {
|
||||
throw new Error("Failed to restart, please check the terminal output for more information.");
|
||||
}
|
||||
return exitCode;
|
||||
}
|
||||
|
||||
async startCombinedTerminal(socket: DockgeSocket) {
|
||||
async joinCombinedTerminal(socket: DockgeSocket) {
|
||||
const terminalName = getCombinedTerminalName(this.name);
|
||||
const terminal = Terminal.getOrCreateTerminal(this.server, terminalName, "docker-compose", [ "logs", "-f" ], this.path);
|
||||
const terminal = Terminal.getOrCreateTerminal(this.server, terminalName, "docker", [ "compose", "logs", "-f" ], this.path);
|
||||
terminal.rows = COMBINED_TERMINAL_ROWS;
|
||||
terminal.cols = COMBINED_TERMINAL_COLS;
|
||||
terminal.join(socket);
|
||||
terminal.start();
|
||||
}
|
||||
|
||||
async joinContainerTerminal(socket: DockgeSocket, serviceName: string, index: number = 0) {
|
||||
const terminalName = getContainerExecTerminalName(this.name, serviceName, index);
|
||||
let terminal = Terminal.getTerminal(terminalName);
|
||||
|
||||
if (!terminal) {
|
||||
terminal = new InteractiveTerminal(this.server, terminalName, "docker", [ "compose", "exec", serviceName, "bash" ], this.path);
|
||||
terminal.rows = TERMINAL_ROWS;
|
||||
log.debug("deployStack", "Terminal created");
|
||||
}
|
||||
|
||||
terminal.join(socket);
|
||||
terminal.start();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,7 +51,11 @@ export class Terminal {
|
|||
|
||||
set rows(rows : number) {
|
||||
this._rows = rows;
|
||||
try {
|
||||
this.ptyProcess?.resize(this.cols, this.rows);
|
||||
} catch (e) {
|
||||
log.debug("Terminal", "Failed to resize terminal: " + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
get cols() {
|
||||
|
@ -60,7 +64,11 @@ export class Terminal {
|
|||
|
||||
set cols(cols : number) {
|
||||
this._cols = cols;
|
||||
try {
|
||||
this.ptyProcess?.resize(this.cols, this.rows);
|
||||
} catch (e) {
|
||||
log.debug("Terminal", "Failed to resize terminal: " + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
public start() {
|
||||
|
@ -133,11 +141,16 @@ export class Terminal {
|
|||
this._ptyProcess?.kill();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a running and non-exited terminal
|
||||
* @param name
|
||||
*/
|
||||
public static getTerminal(name : string) : Terminal | undefined {
|
||||
return Terminal.terminalMap.get(name);
|
||||
}
|
||||
|
||||
public static getOrCreateTerminal(server : DockgeServer, name : string, file : string, args : string | string[], cwd : string) : Terminal {
|
||||
// Since exited terminal will be removed from the map, it is safe to get the terminal from the map
|
||||
let terminal = Terminal.getTerminal(name);
|
||||
if (!terminal) {
|
||||
terminal = new Terminal(server, name, file, args, cwd);
|
||||
|
|
|
@ -84,7 +84,7 @@ export const TERMINAL_COLS = 105;
|
|||
export const TERMINAL_ROWS = 10;
|
||||
export const PROGRESS_TERMINAL_ROWS = 8;
|
||||
|
||||
export const COMBINED_TERMINAL_COLS = 50;
|
||||
export const COMBINED_TERMINAL_COLS = 56;
|
||||
export const COMBINED_TERMINAL_ROWS = 15;
|
||||
|
||||
export const ERROR_TYPE_VALIDATION = 1;
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<div class="function">
|
||||
<router-link v-if="!isEditMode" class="btn btn-normal" :to="terminalRouteLink">
|
||||
<font-awesome-icon icon="terminal" />
|
||||
Terminal
|
||||
Bash
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -160,12 +160,17 @@ export default defineComponent({
|
|||
return {
|
||||
name: "containerTerminal",
|
||||
params: {
|
||||
stackName: this.stackName,
|
||||
serviceName: this.name,
|
||||
type: "logs",
|
||||
type: "bash",
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
stackName() {
|
||||
return this.$parent.$parent.stack.name;
|
||||
},
|
||||
|
||||
service() {
|
||||
return this.jsonObject.services[this.name];
|
||||
},
|
||||
|
|
|
@ -20,7 +20,17 @@ export default {
|
|||
props: {
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
require: true,
|
||||
},
|
||||
|
||||
// Require if mode is interactive
|
||||
stackName: {
|
||||
type: String,
|
||||
},
|
||||
|
||||
// Require if mode is interactive
|
||||
serviceName: {
|
||||
type: String,
|
||||
},
|
||||
|
||||
rows: {
|
||||
|
@ -99,7 +109,8 @@ export default {
|
|||
}
|
||||
});
|
||||
} else if (this.mode === "interactive") {
|
||||
this.$root.getSocket().emit("interactiveTerminal", this.name, (res) => {
|
||||
console.debug("Create Interactive terminal:", this.name);
|
||||
this.$root.getSocket().emit("interactiveTerminal", this.stackName, this.serviceName, (res) => {
|
||||
if (!res.ok) {
|
||||
this.$root.toastRes(res);
|
||||
}
|
||||
|
@ -184,7 +195,13 @@ export default {
|
|||
},
|
||||
|
||||
interactiveTerminalConfig() {
|
||||
|
||||
this.terminal.onKey(e => {
|
||||
this.$root.getSocket().emit("terminalInput", this.name, e.key, (res) => {
|
||||
if (!res.ok) {
|
||||
this.$root.toastRes(res);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -193,6 +210,7 @@ export default {
|
|||
<style scoped lang="scss">
|
||||
.main-terminal {
|
||||
height: 100%;
|
||||
overflow-x: scroll;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
|
|
@ -141,7 +141,7 @@ export default defineComponent({
|
|||
socket.on("terminalWrite", (terminalName, data) => {
|
||||
const terminal = terminalMap.get(terminalName);
|
||||
if (!terminal) {
|
||||
console.error("Terminal not found: " + terminalName);
|
||||
//console.error("Terminal not found: " + terminalName);
|
||||
return;
|
||||
}
|
||||
terminal.write(data);
|
||||
|
|
|
@ -104,7 +104,7 @@
|
|||
|
||||
<!-- Combined Terminal Output -->
|
||||
<div v-show="!isEditMode">
|
||||
<h4 class="mb-3">Logs</h4>
|
||||
<h4 class="mb-3">Terminal</h4>
|
||||
<Terminal
|
||||
ref="combinedTerminal"
|
||||
class="mb-3 terminal"
|
||||
|
@ -135,6 +135,7 @@
|
|||
{{ yamlError }}
|
||||
</div>
|
||||
|
||||
<div v-if="isEditMode">
|
||||
<h4 class="mb-3">{{ $tc("network", 2) }}</h4>
|
||||
<div class="shadow-box big-padding mb-3">
|
||||
<NetworkInput />
|
||||
|
@ -143,6 +144,7 @@
|
|||
<h4 class="mb-3">{{ $tc("volume", 2) }}</h4>
|
||||
<div class="shadow-box big-padding mb-3">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- <div class="shadow-box big-padding mb-3">
|
||||
<div class="mb-3">
|
||||
|
@ -531,43 +533,6 @@ export default {
|
|||
});
|
||||
},
|
||||
|
||||
combineNetworks() {
|
||||
let networks = this.jsonConfig.networks;
|
||||
|
||||
if (!networks) {
|
||||
networks = {};
|
||||
}
|
||||
|
||||
for (let serviceName in this.jsonConfig.services) {
|
||||
|
||||
let service = this.jsonConfig.services[serviceName];
|
||||
let serviceNetworks = service.networks;
|
||||
|
||||
if (!networks) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If it is an array, it should be array of string
|
||||
if (Array.isArray(serviceNetworks)) {
|
||||
for (let n of serviceNetworks) {
|
||||
console.log(n);
|
||||
if (!n) {
|
||||
continue;
|
||||
}
|
||||
if (!networks[n]) {
|
||||
networks[n] = {};
|
||||
}
|
||||
}
|
||||
|
||||
} else if (typeof serviceNetworks === "object") {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
console.debug(networks);
|
||||
|
||||
return networks;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -1,37 +1,39 @@
|
|||
<template>
|
||||
<transition name="slide-fade" appear>
|
||||
<div>
|
||||
<h1 class="mb-3">Console</h1>
|
||||
|
||||
<div>
|
||||
<h1 class="mb-3">Bash</h1>
|
||||
<p>
|
||||
Allowed commands:
|
||||
<template v-for="(command, index) in allowedCommandList" :key="command">
|
||||
<code>{{ command }}</code>
|
||||
|
||||
<!-- No comma at the end -->
|
||||
<span v-if="index !== allowedCommandList.length - 1">, </span>
|
||||
</template>
|
||||
Stack: {{ stackName }}<br />
|
||||
Container: {{ serviceName }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Terminal class="terminal" :rows="20" mode="mainTerminal" name="console"></Terminal>
|
||||
<Terminal class="terminal" :rows="20" mode="interactive" :name="terminalName" :stack-name="stackName" :service-name="serviceName"></Terminal>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import { allowedCommandList } from "../../../backend/util-common";
|
||||
import { getContainerExecTerminalName } from "../../../backend/util-common";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
allowedCommandList,
|
||||
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
stackName() {
|
||||
return this.$route.params.stackName;
|
||||
},
|
||||
serviceName() {
|
||||
return this.$route.params.serviceName;
|
||||
},
|
||||
terminalName() {
|
||||
return getContainerExecTerminalName(this.stackName, this.serviceName, 0);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
||||
},
|
||||
|
|
|
@ -39,16 +39,14 @@ const routes = [
|
|||
name: "compose",
|
||||
component: Compose,
|
||||
props: true,
|
||||
children: [
|
||||
},
|
||||
{
|
||||
path: "/compose/:stackName/terminal/:serviceName/:type",
|
||||
path: "/terminal/:stackName/:serviceName/:type",
|
||||
component: ContainerTerminal,
|
||||
name: "containerTerminal",
|
||||
},
|
||||
]
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
path: "/console",
|
||||
component: Console,
|
||||
|
|
Loading…
Reference in a new issue