This commit is contained in:
Dominika Jadowska 2025-01-27 16:37:31 +01:00
parent d451e06e84
commit 74b60c6f61
5 changed files with 10483 additions and 6 deletions

View file

@ -67,6 +67,25 @@ export class DockerSocketHandler extends AgentSocketHandler {
} }
}); });
agentSocket.on("forceDeleteStack", async (name : unknown, callback) => {
try {
checkLogin(socket);
if (typeof(name) !== "string") {
throw new ValidationError("Name must be a string");
}
const stack = await Stack.getStack(server, name);
await stack.forceDelete(socket);
server.sendStackList();
callbackResult({
ok: true,
msg: "Deleted",
msgi18n: true,
}, callback);
} catch (e) {
callbackError(e, callback);
}
});
agentSocket.on("getStack", async (stackName : unknown, callback) => { agentSocket.on("getStack", async (stackName : unknown, callback) => {
try { try {
checkLogin(socket); checkLogin(socket);

View file

@ -219,7 +219,7 @@ export class Stack {
const terminalName = getComposeTerminalName(socket.endpoint, this.name); const terminalName = getComposeTerminalName(socket.endpoint, this.name);
let exitCode = await Terminal.exec(this.server, socket, terminalName, "docker", ["compose", "down", "--remove-orphans"], this.path); let exitCode = await Terminal.exec(this.server, socket, terminalName, "docker", ["compose", "down", "--remove-orphans"], this.path);
if (exitCode !== 0) { if (exitCode !== 0) {
throw new Error("Failed to delete, please check the terminal output for more information."); throw new Error(`Failed to delete ${this.name}, please check the terminal output for more information.`);
} }
// Remove the stack folder // Remove the stack folder
@ -231,6 +231,19 @@ export class Stack {
return exitCode; return exitCode;
} }
async forceDelete(socket: DockgeSocket): Promise<number> {
const terminalName = getComposeTerminalName(socket.endpoint, this.name);
let exitCode = await Terminal.exec(this.server, socket, terminalName, "docker", ["compose", "down", "-v", "--remove-orphans"], this.path);
// Remove the stack folder
await fsAsync.rm(this.path, {
recursive: true,
force: true
});
return exitCode;
}
async updateStatus() { async updateStatus() {
let statusList = await Stack.getStatusList(); let statusList = await Stack.getStatusList();
let status = statusList.get(this.name); let status = statusList.get(this.name);

View file

@ -15,6 +15,7 @@
"stackName": "Stack Name", "stackName": "Stack Name",
"deployStack": "Deploy", "deployStack": "Deploy",
"deleteStack": "Delete", "deleteStack": "Delete",
"forceDeleteStack": "Force Delete",
"stopStack": "Stop", "stopStack": "Stop",
"restartStack": "Restart", "restartStack": "Restart",
"updateStack": "Update", "updateStack": "Update",
@ -25,6 +26,7 @@
"saveStackDraft": "Save", "saveStackDraft": "Save",
"notAvailableShort": "N/A", "notAvailableShort": "N/A",
"deleteStackMsg": "Are you sure you want to delete this stack?", "deleteStackMsg": "Are you sure you want to delete this stack?",
"forceDeleteStackMsg": "Force deleting may leave behind some files or configuration. Are you sure you want to force delete this stack?",
"stackNotManagedByDockgeMsg": "This stack is not managed by Dockge.", "stackNotManagedByDockgeMsg": "This stack is not managed by Dockge.",
"primaryHostname": "Primary Hostname", "primaryHostname": "Primary Hostname",
"general": "General", "general": "General",

View file

@ -55,10 +55,14 @@
</div> </div>
<button v-if="isEditMode && !isAdd" class="btn btn-normal" :disabled="processing" @click="discardStack">{{ $t("discardStack") }}</button> <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"> <button v-if="!isEditMode && !errorDelete" class="btn btn-danger" :disabled="processing" @click="showDeleteDialog = !showDeleteDialog">
<font-awesome-icon icon="trash" class="me-1" /> <font-awesome-icon icon="trash" class="me-1" />
{{ $t("deleteStack") }} {{ $t("deleteStack") }}
</button> </button>
<button v-if="errorDelete" class="btn btn-danger" :disabled="processing" @click="showForceDeleteDialog = !showForceDeleteDialog">
<font-awesome-icon icon="trash" class="me-1" />
{{ $t("forceDeleteStack") }}
</button>
</div> </div>
<!-- URLs --> <!-- URLs -->
@ -232,6 +236,11 @@
<BModal v-model="showDeleteDialog" :okTitle="$t('deleteStack')" okVariant="danger" @ok="deleteDialog"> <BModal v-model="showDeleteDialog" :okTitle="$t('deleteStack')" okVariant="danger" @ok="deleteDialog">
{{ $t("deleteStackMsg") }} {{ $t("deleteStackMsg") }}
</BModal> </BModal>
<!-- Force Delete Dialog -->
<BModal v-model="showForceDeleteDialog" :okTitle="$t('forceDeleteStack')" okVariant="danger" @ok="forceDeleteDialog">
{{ $t("forceDeleteStackMsg") }}
</BModal>
</div> </div>
</transition> </transition>
</template> </template>
@ -307,8 +316,10 @@ export default {
}, },
serviceStatusList: {}, serviceStatusList: {},
isEditMode: false, isEditMode: false,
errorDelete: false,
submitted: false, submitted: false,
showDeleteDialog: false, showDeleteDialog: false,
showForceDeleteDialog: false,
newContainerName: "", newContainerName: "",
stopServiceStatusTimeout: false, stopServiceStatusTimeout: false,
}; };
@ -647,6 +658,17 @@ export default {
deleteDialog() { deleteDialog() {
this.$root.emitAgent(this.endpoint, "deleteStack", this.stack.name, (res) => { this.$root.emitAgent(this.endpoint, "deleteStack", this.stack.name, (res) => {
this.$root.toastRes(res);
if (res.ok) {
this.$router.push("/");
} else {
this.errorDelete = true;
}
});
},
forceDeleteDialog() {
this.$root.emitAgent(this.endpoint, "forceDeleteStack", this.stack.name, (res) => {
this.$root.toastRes(res); this.$root.toastRes(res);
if (res.ok) { if (res.ok) {
this.$router.push("/"); this.$router.push("/");

10421
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff