mirror of
https://github.com/louislam/dockge.git
synced 2024-11-28 11:14:31 +01:00
wip
This commit is contained in:
parent
2530cac989
commit
caa82bbad5
@ -29,6 +29,7 @@ import { Stack } from "./stack";
|
||||
import { Cron } from "croner";
|
||||
import gracefulShutdown from "http-graceful-shutdown";
|
||||
import User from "./models/user";
|
||||
import childProcess from "child_process";
|
||||
|
||||
export class DockgeServer {
|
||||
app : Express;
|
||||
@ -516,6 +517,20 @@ export class DockgeServer {
|
||||
}
|
||||
}
|
||||
|
||||
getDockerNetworkList() : string[] {
|
||||
let res = childProcess.spawnSync("docker", [ "network", "ls", "--format", "{{.Name}}" ]);
|
||||
let list = res.stdout.toString().split("\n");
|
||||
|
||||
// Remove empty string item
|
||||
list = list.filter((item) => {
|
||||
return item !== "";
|
||||
}).sort((a, b) => {
|
||||
return a.localeCompare(b);
|
||||
});
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
get stackDirFullPath() {
|
||||
return path.resolve(this.stacksDir);
|
||||
}
|
||||
|
@ -203,6 +203,20 @@ export class DockerSocketHandler extends SocketHandler {
|
||||
callbackError(e, callback);
|
||||
}
|
||||
});
|
||||
|
||||
// getExternalNetworkList
|
||||
socket.on("getDockerNetworkList", async (callback) => {
|
||||
try {
|
||||
checkLogin(socket);
|
||||
const dockerNetworkList = server.getDockerNetworkList();
|
||||
callback({
|
||||
ok: true,
|
||||
dockerNetworkList,
|
||||
});
|
||||
} catch (e) {
|
||||
callbackError(e, callback);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
saveStack(socket : DockgeSocket, server : DockgeServer, name : unknown, composeYAML : unknown, isAdd : unknown) : Stack {
|
||||
|
1
frontend/components.d.ts
vendored
1
frontend/components.d.ts
vendored
@ -10,6 +10,7 @@ declare module 'vue' {
|
||||
About: typeof import('./src/components/settings/About.vue')['default']
|
||||
Appearance: typeof import('./src/components/settings/Appearance.vue')['default']
|
||||
ArrayInput: typeof import('./src/components/ArrayInput.vue')['default']
|
||||
ArraySelect: typeof import('./src/components/ArraySelect.vue')['default']
|
||||
BModal: typeof import('bootstrap-vue-next')['BModal']
|
||||
Confirm: typeof import('./src/components/Confirm.vue')['default']
|
||||
Container: typeof import('./src/components/Container.vue')['default']
|
||||
|
125
frontend/src/components/ArraySelect.vue
Normal file
125
frontend/src/components/ArraySelect.vue
Normal file
@ -0,0 +1,125 @@
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="valid">
|
||||
<ul v-if="isArrayInited" class="list-group">
|
||||
<li v-for="(value, index) in array" :key="index" class="list-group-item">
|
||||
<select v-model="array[index]" class="no-bg domain-input">
|
||||
<option value="">Select a network...</option>
|
||||
<option v-for="option in options" :value="option">{{ option }}</option>
|
||||
</select>
|
||||
|
||||
<font-awesome-icon icon="times" class="action remove ms-2 me-3 text-danger" @click="remove(index)" />
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<button class="btn btn-normal btn-sm mt-3" @click="addField">{{ $t("addListItem", [ displayName ]) }}</button>
|
||||
</div>
|
||||
<div v-else>
|
||||
Long syntax is not supported here. Please use the YAML editor.
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
displayName: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
options: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
array() {
|
||||
// Create the array if not exists, it should be safe.
|
||||
if (!this.service[this.name]) {
|
||||
// eslint-disable-next-line vue/no-side-effects-in-computed-properties
|
||||
this.service[this.name] = [];
|
||||
}
|
||||
return this.service[this.name];
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if the array is inited before called v-for.
|
||||
* Prevent empty arrays inserted to the YAML file.
|
||||
* @return {boolean}
|
||||
*/
|
||||
isArrayInited() {
|
||||
return this.service[this.name] !== undefined;
|
||||
},
|
||||
|
||||
service() {
|
||||
return this.$parent.$parent.service;
|
||||
},
|
||||
|
||||
valid() {
|
||||
// Check if the array is actually an array
|
||||
if (!Array.isArray(this.array)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the array contains non-object only.
|
||||
for (let item of this.array) {
|
||||
if (typeof item === "object") {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
},
|
||||
created() {
|
||||
|
||||
},
|
||||
methods: {
|
||||
addField() {
|
||||
this.array.push("");
|
||||
},
|
||||
remove(index) {
|
||||
this.array.splice(index, 1);
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../styles/vars.scss";
|
||||
|
||||
.list-group {
|
||||
background-color: $dark-bg2;
|
||||
|
||||
li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 10px 0 10px 10px;
|
||||
|
||||
.domain-input {
|
||||
flex-grow: 1;
|
||||
background-color: $dark-bg2;
|
||||
border: none;
|
||||
color: $dark-font-color;
|
||||
outline: none;
|
||||
|
||||
&::placeholder {
|
||||
color: #1d2634;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -114,7 +114,12 @@
|
||||
<label class="form-label">
|
||||
{{ $tc("network", 2) }}
|
||||
</label>
|
||||
<ArrayInput name="networks" :display-name="$t('network')" placeholder="Network Name" />
|
||||
|
||||
<div v-if="networkList.length === 0 && service.networks.length > 0" class="text-warning mb-3">
|
||||
No networks available. You need to add internal networks or enable external networks in the right side first.
|
||||
</div>
|
||||
|
||||
<ArraySelect name="networks" :display-name="$t('network')" placeholder="Network Name" :options="networkList" />
|
||||
</div>
|
||||
|
||||
<!-- Depends on -->
|
||||
@ -165,6 +170,14 @@ export default defineComponent({
|
||||
},
|
||||
computed: {
|
||||
|
||||
networkList() {
|
||||
let list = [];
|
||||
for (const networkName in this.jsonObject.networks) {
|
||||
list.push(networkName);
|
||||
}
|
||||
return list;
|
||||
},
|
||||
|
||||
bgStyle() {
|
||||
if (this.status === "running") {
|
||||
return "bg-primary";
|
||||
|
@ -1,69 +1,157 @@
|
||||
<template>
|
||||
<div>
|
||||
<h5>Internal Networks</h5>
|
||||
|
||||
<ul class="list-group">
|
||||
<li v-for="(networkRow, index) in networkList" :key="index" class="list-group-item">
|
||||
<input v-model="networkList[index].key" type="text" class="no-bg domain-input" placeholder="Network name..." />
|
||||
<input v-model="networkRow.key" type="text" class="no-bg domain-input" placeholder="Network name..." />
|
||||
<font-awesome-icon icon="times" class="action remove ms-2 me-3 text-danger" @click="remove(index)" />
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<button class="btn btn-normal btn-sm mt-3" @click="addField">{{ $t("addNetwork") }}</button>
|
||||
<button class="btn btn-normal btn-sm mt-3 me-2" @click="addField">{{ $t("addInternalNetwork") }}</button>
|
||||
|
||||
<h5 class="mt-3">External Networks</h5>
|
||||
|
||||
<div v-if="externalNetworkList.length === 0">
|
||||
No External Networks
|
||||
</div>
|
||||
|
||||
<div v-for="(networkName, index) in externalNetworkList" :key="networkName" class="form-check form-switch my-3">
|
||||
<input :id=" 'external-network' + index" v-model="selectedExternalList[networkName]" class="form-check-input" type="checkbox">
|
||||
|
||||
<label class="form-check-label" :for=" 'external-network' +index">
|
||||
{{ networkName }}
|
||||
</label>
|
||||
|
||||
<span v-if="false" class="text-danger ms-2 delete">Delete</span>
|
||||
</div>
|
||||
|
||||
<div v-if="false" class="input-group mb-3">
|
||||
<input
|
||||
placeholder="New external network name..."
|
||||
class="form-control"
|
||||
@keyup.enter="createExternelNetwork"
|
||||
/>
|
||||
<button class="btn btn-normal btn-sm me-2" type="button" @click="">
|
||||
{{ $t("createExternalNetwork") }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-if="false">
|
||||
<button class="btn btn-primary btn-sm mt-3 me-2" @click="applyToYAML">{{ $t("applyToYAML") }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
networkList: [],
|
||||
externalList: {},
|
||||
selectedExternalList: {},
|
||||
externalNetworkList: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
||||
isInited() {
|
||||
return this.networks !== undefined;
|
||||
jsonConfig() {
|
||||
return this.$parent.$parent.jsonConfig;
|
||||
},
|
||||
|
||||
networks() {
|
||||
return this.$parent.$parent.networks;
|
||||
stack() {
|
||||
return this.$parent.$parent.stack;
|
||||
},
|
||||
|
||||
editorFocus() {
|
||||
return this.$parent.$parent.editorFocus;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
networks: {
|
||||
"jsonConfig.networks": {
|
||||
handler() {
|
||||
this.loadNetworkList();
|
||||
if (this.editorFocus) {
|
||||
console.debug("jsonConfig.networks changed");
|
||||
this.loadNetworkList();
|
||||
}
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
networkList: {
|
||||
|
||||
"selectedExternalList": {
|
||||
handler() {
|
||||
for (const networkName in this.selectedExternalList) {
|
||||
const enable = this.selectedExternalList[networkName];
|
||||
|
||||
if (enable) {
|
||||
if (!this.externalList[networkName]) {
|
||||
this.externalList[networkName] = {};
|
||||
}
|
||||
this.externalList[networkName].external = true;
|
||||
} else {
|
||||
delete this.externalList[networkName];
|
||||
}
|
||||
}
|
||||
this.applyToYAML();
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
|
||||
"networkList": {
|
||||
handler() {
|
||||
this.applyToYAML();
|
||||
},
|
||||
deep: true,
|
||||
}
|
||||
|
||||
},
|
||||
mounted() {
|
||||
this.loadNetworkList();
|
||||
this.loadExternalNetworkList();
|
||||
},
|
||||
methods: {
|
||||
|
||||
loadNetworkList() {
|
||||
console.debug("loadNetworkList", this.networks);
|
||||
|
||||
this.networkList = [];
|
||||
for (const key in this.networks) {
|
||||
this.networkList.push({
|
||||
this.externalList = {};
|
||||
|
||||
for (const key in this.jsonConfig.networks) {
|
||||
let obj = {
|
||||
key: key,
|
||||
value: this.networks[key],
|
||||
});
|
||||
value: this.jsonConfig.networks[key],
|
||||
};
|
||||
|
||||
if (obj.value && obj.value.external) {
|
||||
this.externalList[key] = Object.assign({}, obj.value);
|
||||
} else {
|
||||
this.networkList.push(obj);
|
||||
}
|
||||
}
|
||||
|
||||
// Restore selectedExternalList
|
||||
this.selectedExternalList = {};
|
||||
for (const networkName in this.externalList) {
|
||||
this.selectedExternalList[networkName] = true;
|
||||
}
|
||||
},
|
||||
|
||||
loadExternalNetworkList() {
|
||||
this.$root.getSocket().emit("getDockerNetworkList", (res) => {
|
||||
if (res.ok) {
|
||||
this.externalNetworkList = res.dockerNetworkList.filter((n) => {
|
||||
// Filter out this stack networks
|
||||
if (n.startsWith(this.stack.name + "_")) {
|
||||
return false;
|
||||
}
|
||||
// They should be not supported.
|
||||
// https://docs.docker.com/compose/compose-file/06-networks/#host-or-none
|
||||
if (n === "none" || n === "host" || n === "bridge") {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
} else {
|
||||
this.$root.toastRes(res);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
addField() {
|
||||
@ -72,10 +160,33 @@ export default {
|
||||
value: {},
|
||||
});
|
||||
},
|
||||
|
||||
remove(index) {
|
||||
this.networkList.splice(index, 1);
|
||||
this.applyToYAML();
|
||||
},
|
||||
}
|
||||
|
||||
applyToYAML() {
|
||||
if (this.editorFocus) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.jsonConfig.networks = {};
|
||||
|
||||
// Internal networks
|
||||
for (const networkRow of this.networkList) {
|
||||
this.jsonConfig.networks[networkRow.key] = networkRow.value;
|
||||
}
|
||||
|
||||
// External networks
|
||||
for (const networkName in this.externalList) {
|
||||
this.jsonConfig.networks[networkName] = this.externalList[networkName];
|
||||
}
|
||||
|
||||
console.debug("applyToYAML", this.jsonConfig.networks);
|
||||
}
|
||||
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@ -103,4 +214,10 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.delete {
|
||||
text-decoration: underline;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
@ -45,5 +45,9 @@
|
||||
"disableauth.message1": "Are you sure want to <strong>disable authentication</strong>?",
|
||||
"disableauth.message2": "It is designed for scenarios <strong>where you intend to implement third-party authentication</strong> in front of Uptime Kuma such as Cloudflare Access, Authelia or other authentication mechanisms.",
|
||||
"passwordNotMatchMsg": "The repeat password does not match.",
|
||||
"autoGet": "Auto Get"
|
||||
"autoGet": "Auto Get",
|
||||
"add": "Add",
|
||||
"applyToYAML": "Apply to YAML",
|
||||
"createExternalNetwork": "Create",
|
||||
"addInternalNetwork": "Add"
|
||||
}
|
||||
|
@ -137,14 +137,18 @@
|
||||
</div>
|
||||
|
||||
<div v-if="isEditMode">
|
||||
<!-- Volumes -->
|
||||
<div v-if="false">
|
||||
<h4 class="mb-3">{{ $tc("volume", 2) }}</h4>
|
||||
<div class="shadow-box big-padding mb-3">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Networks -->
|
||||
<h4 class="mb-3">{{ $tc("network", 2) }}</h4>
|
||||
<div class="shadow-box big-padding mb-3">
|
||||
<NetworkInput />
|
||||
</div>
|
||||
|
||||
<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">
|
||||
|
@ -73,7 +73,7 @@
|
||||
"vue-qrcode": "~2.2.0",
|
||||
"vue-router": "~4.2.5",
|
||||
"vue-toastification": "2.0.0-rc.5",
|
||||
"xterm": "~5.3.0",
|
||||
"xterm": "~5.4.0-beta.37",
|
||||
"xterm-addon-web-links": "~0.9.0"
|
||||
}
|
||||
}
|
||||
|
@ -173,11 +173,11 @@ devDependencies:
|
||||
specifier: 2.0.0-rc.5
|
||||
version: 2.0.0-rc.5(vue@3.3.8)
|
||||
xterm:
|
||||
specifier: ~5.3.0
|
||||
version: 5.3.0
|
||||
specifier: ~5.4.0-beta.37
|
||||
version: 5.4.0-beta.37
|
||||
xterm-addon-web-links:
|
||||
specifier: ~0.9.0
|
||||
version: 0.9.0(xterm@5.3.0)
|
||||
version: 0.9.0(xterm@5.4.0-beta.37)
|
||||
|
||||
packages:
|
||||
|
||||
@ -4406,16 +4406,16 @@ packages:
|
||||
engines: {node: '>=0.4.0'}
|
||||
dev: false
|
||||
|
||||
/xterm-addon-web-links@0.9.0(xterm@5.3.0):
|
||||
/xterm-addon-web-links@0.9.0(xterm@5.4.0-beta.37):
|
||||
resolution: {integrity: sha512-LIzi4jBbPlrKMZF3ihoyqayWyTXAwGfu4yprz1aK2p71e9UKXN6RRzVONR0L+Zd+Ik5tPVI9bwp9e8fDTQh49Q==}
|
||||
peerDependencies:
|
||||
xterm: ^5.0.0
|
||||
dependencies:
|
||||
xterm: 5.3.0
|
||||
xterm: 5.4.0-beta.37
|
||||
dev: true
|
||||
|
||||
/xterm@5.3.0:
|
||||
resolution: {integrity: sha512-8QqjlekLUFTrU6x7xck1MsPzPA571K5zNqWm0M0oroYEWVOptZ0+ubQSkQ3uxIEhcIHRujJy6emDWX4A7qyFzg==}
|
||||
/xterm@5.4.0-beta.37:
|
||||
resolution: {integrity: sha512-ys+mXqLFrJc7khmYN/MgBnfLv38NgXfkwkEXsCZKHGqn3h2xUBvTvsrSEWO3NQeDPLj4zMr1RwqTblMK9St3BA==}
|
||||
dev: true
|
||||
|
||||
/y18n@4.0.3:
|
||||
|
Loading…
Reference in New Issue
Block a user