diff --git a/backend/agent-socket-handlers/docker-socket-handler.ts b/backend/agent-socket-handlers/docker-socket-handler.ts index 8174601..eb0713f 100644 --- a/backend/agent-socket-handlers/docker-socket-handler.ts +++ b/backend/agent-socket-handlers/docker-socket-handler.ts @@ -1,7 +1,7 @@ import { AgentSocketHandler } from "../agent-socket-handler"; import { DockgeServer } from "../dockge-server"; import { callbackError, callbackResult, checkLogin, DockgeSocket, ValidationError } from "../util-server"; -import { Stack } from "../stack"; +import { DeleteOptions, Stack } from "../stack"; import { AgentSocket } from "../../common/agent-socket"; export class DockerSocketHandler extends AgentSocketHandler { @@ -40,7 +40,7 @@ export class DockerSocketHandler extends AgentSocketHandler { } }); - agentSocket.on("deleteStack", async (name : unknown, callback) => { + agentSocket.on("deleteStack", async (name : unknown, deleteOptions: unknown, callback) => { try { checkLogin(socket); if (typeof(name) !== "string") { @@ -49,7 +49,7 @@ export class DockerSocketHandler extends AgentSocketHandler { const stack = await Stack.getStack(server, name); try { - await stack.delete(socket); + await stack.delete(socket, deleteOptions as DeleteOptions); } catch (e) { server.sendStackList(); throw e; diff --git a/backend/stack.ts b/backend/stack.ts index fbce500..1defb25 100644 --- a/backend/stack.ts +++ b/backend/stack.ts @@ -20,6 +20,10 @@ import { InteractiveTerminal, Terminal } from "./terminal"; import childProcessAsync from "promisify-child-process"; import { Settings } from "./settings"; +export interface DeleteOptions { + deleteStackFiles: boolean +} + export class Stack { name: string; @@ -215,18 +219,20 @@ export class Stack { return exitCode; } - async delete(socket: DockgeSocket) : Promise<number> { + async delete(socket: DockgeSocket, options: DeleteOptions) : Promise<number> { const terminalName = getComposeTerminalName(socket.endpoint, this.name); let exitCode = await Terminal.exec(this.server, socket, terminalName, "docker", [ "compose", "down", "--remove-orphans" ], this.path); if (exitCode !== 0) { throw new Error("Failed to delete, please check the terminal output for more information."); } - // Remove the stack folder - await fsAsync.rm(this.path, { - recursive: true, - force: true - }); + if (options.deleteStackFiles) { + // Remove the stack folder + await fsAsync.rm(this.path, { + recursive: true, + force: true + }); + } return exitCode; } diff --git a/frontend/src/lang/en.json b/frontend/src/lang/en.json index 0636226..6b0adf3 100644 --- a/frontend/src/lang/en.json +++ b/frontend/src/lang/en.json @@ -25,6 +25,7 @@ "saveStackDraft": "Save", "notAvailableShort": "N/A", "deleteStackMsg": "Are you sure you want to delete this stack?", + "deleteStackFilesConfirmation": "delete all stack files", "stackNotManagedByDockgeMsg": "This stack is not managed by Dockge.", "primaryHostname": "Primary Hostname", "general": "General", diff --git a/frontend/src/pages/Compose.vue b/frontend/src/pages/Compose.vue index bcea292..b07bb4e 100644 --- a/frontend/src/pages/Compose.vue +++ b/frontend/src/pages/Compose.vue @@ -55,6 +55,7 @@ </div> <button v-if="isEditMode && !isAdd" class="btn btn-normal" :disabled="processing" @click="discardStack">{{ $t("discardStack") }}</button> + <button v-if="!isEditMode" class="btn btn-danger" :disabled="processing" @click="showDeleteDialog = !showDeleteDialog"> <font-awesome-icon icon="trash" class="me-1" /> {{ $t("deleteStack") }} @@ -231,6 +232,10 @@ <!-- Delete Dialog --> <BModal v-model="showDeleteDialog" :okTitle="$t('deleteStack')" okVariant="danger" @ok="deleteDialog"> {{ $t("deleteStackMsg") }} + <div class="form-check mt-4"> + <label><input v-model="deleteStackFiles" class="form-check-input" type="checkbox" />{{ + $t("deleteStackFilesConfirmation") }}</label> + </div> </BModal> </div> </transition> @@ -309,6 +314,7 @@ export default { isEditMode: false, submitted: false, showDeleteDialog: false, + deleteStackFiles: false, newContainerName: "", stopServiceStatusTimeout: false, }; @@ -651,7 +657,7 @@ export default { }, deleteDialog() { - this.$root.emitAgent(this.endpoint, "deleteStack", this.stack.name, (res) => { + this.$root.emitAgent(this.endpoint, "deleteStack", this.stack.name, { deleteStackFiles: this.deleteStackFiles }, (res) => { this.$root.toastRes(res); if (res.ok) { this.$router.push("/");