2023-11-11 22:18:37 +08:00
|
|
|
<template>
|
|
|
|
<transition ref="tableContainer" name="slide-fade" appear>
|
|
|
|
<div v-if="$route.name === 'DashboardHome'">
|
|
|
|
<h1 class="mb-3">
|
|
|
|
{{ $t("home") }}
|
|
|
|
</h1>
|
|
|
|
|
2023-12-26 04:12:44 +08:00
|
|
|
<div class="row first-row">
|
|
|
|
<!-- Left -->
|
|
|
|
<div class="col-md-7">
|
|
|
|
<!-- Stats -->
|
|
|
|
<div class="shadow-box big-padding text-center mb-4">
|
|
|
|
<div class="row">
|
|
|
|
<div class="col">
|
|
|
|
<h3>{{ $t("active") }}</h3>
|
|
|
|
<span class="num active">{{ activeNum }}</span>
|
|
|
|
</div>
|
|
|
|
<div class="col">
|
|
|
|
<h3>{{ $t("exited") }}</h3>
|
|
|
|
<span class="num exited">{{ exitedNum }}</span>
|
|
|
|
</div>
|
|
|
|
<div class="col">
|
|
|
|
<h3>{{ $t("inactive") }}</h3>
|
|
|
|
<span class="num inactive">{{ inactiveNum }}</span>
|
|
|
|
</div>
|
|
|
|
</div>
|
2023-11-11 22:18:37 +08:00
|
|
|
</div>
|
2023-12-26 04:12:44 +08:00
|
|
|
|
|
|
|
<!-- Docker Run -->
|
|
|
|
<h2 class="mb-3">{{ $t("Docker Run") }}</h2>
|
|
|
|
<div class="mb-3">
|
|
|
|
<textarea id="name" v-model="dockerRunCommand" type="text" class="form-control docker-run" required placeholder="docker run ..."></textarea>
|
2023-11-11 22:18:37 +08:00
|
|
|
</div>
|
2023-12-26 04:12:44 +08:00
|
|
|
|
|
|
|
<button class="btn-normal btn mb-4" @click="convertDockerRun">{{ $t("Convert to Compose") }}</button>
|
|
|
|
</div>
|
|
|
|
<!-- Right -->
|
|
|
|
<div class="col-md-5">
|
|
|
|
<!-- Agent List -->
|
|
|
|
<div class="shadow-box big-padding">
|
|
|
|
<h4 class="mb-3">{{ $tc("dockgeAgent", 2) }} <span class="badge bg-warning" style="font-size: 12px;">beta</span></h4>
|
|
|
|
|
|
|
|
<div v-for="(agent, endpoint) in $root.agentList" :key="endpoint" class="mb-3 agent">
|
|
|
|
<!-- Agent Status -->
|
|
|
|
<template v-if="$root.agentStatusList[endpoint]">
|
|
|
|
<span v-if="$root.agentStatusList[endpoint] === 'online'" class="badge bg-primary me-2">{{ $t("agentOnline") }}</span>
|
|
|
|
<span v-else-if="$root.agentStatusList[endpoint] === 'offline'" class="badge bg-danger me-2">{{ $t("agentOffline") }}</span>
|
|
|
|
<span v-else class="badge bg-secondary me-2">{{ $t($root.agentStatusList[endpoint]) }}</span>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<!-- Agent Display Name -->
|
2024-02-01 07:18:52 +00:00
|
|
|
<template v-if="$root.agentStatusList[endpoint]">
|
2024-08-06 16:01:14 +00:00
|
|
|
<span v-if="endpoint === '' && agent.name === ''" class="badge bg-secondary me-2">Controller</span>
|
2024-08-20 07:57:58 +00:00
|
|
|
<span v-else-if="agent.name === ''" :href="agent.url" class="me-2">{{ endpoint }}</span>
|
|
|
|
<span v-else :href="agent.url" class="me-2">{{ agent.name }}</span>
|
2024-02-01 07:18:52 +00:00
|
|
|
</template>
|
2023-12-26 04:12:44 +08:00
|
|
|
|
2024-08-06 16:01:14 +00:00
|
|
|
<!-- Edit Name -->
|
|
|
|
<font-awesome-icon icon="pen-to-square" @click="showEditAgentNameDialog[agent.name] = !showEditAgentNameDialog[agent.Name]" />
|
2024-02-01 18:54:52 +00:00
|
|
|
|
|
|
|
<!-- Edit Dialog -->
|
2024-08-06 16:01:14 +00:00
|
|
|
<BModal v-model="showEditAgentNameDialog[agent.name]" :no-close-on-backdrop="true" :close-on-esc="true" :okTitle="$t('Update Name')" okVariant="info" @ok="updateName(agent.url, agent.updatedName)">
|
|
|
|
<label for="Update Name" class="form-label">Current value: {{ $t(agent.name) }}</label>
|
|
|
|
<input id="updatedName" v-model="agent.updatedName" type="text" class="form-control" optional>
|
2024-02-01 18:54:52 +00:00
|
|
|
</BModal>
|
|
|
|
|
2023-12-26 04:12:44 +08:00
|
|
|
<!-- Remove Button -->
|
|
|
|
<font-awesome-icon v-if="endpoint !== ''" class="ms-2 remove-agent" icon="trash" @click="showRemoveAgentDialog[agent.url] = !showRemoveAgentDialog[agent.url]" />
|
|
|
|
|
2024-02-01 18:54:52 +00:00
|
|
|
<!-- Remove Agent Dialog -->
|
2023-12-26 04:12:44 +08:00
|
|
|
<BModal v-model="showRemoveAgentDialog[agent.url]" :okTitle="$t('removeAgent')" okVariant="danger" @ok="removeAgent(agent.url)">
|
|
|
|
<p>{{ agent.url }}</p>
|
|
|
|
{{ $t("removeAgentMsg") }}
|
|
|
|
</BModal>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<button v-if="!showAgentForm" class="btn btn-normal" @click="showAgentForm = !showAgentForm">{{ $t("addAgent") }}</button>
|
|
|
|
|
|
|
|
<!-- Add Agent Form -->
|
|
|
|
<form v-if="showAgentForm" @submit.prevent="addAgent">
|
|
|
|
<div class="mb-3">
|
|
|
|
<label for="url" class="form-label">{{ $t("dockgeURL") }}</label>
|
|
|
|
<input id="url" v-model="agent.url" type="url" class="form-control" required placeholder="http://">
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="mb-3">
|
|
|
|
<label for="username" class="form-label">{{ $t("Username") }}</label>
|
|
|
|
<input id="username" v-model="agent.username" type="text" class="form-control" required>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="mb-3">
|
|
|
|
<label for="password" class="form-label">{{ $t("Password") }}</label>
|
|
|
|
<input id="password" v-model="agent.password" type="password" class="form-control" required autocomplete="new-password">
|
|
|
|
</div>
|
|
|
|
|
2024-02-01 07:18:52 +00:00
|
|
|
<div class="mb-3">
|
2024-08-06 16:01:14 +00:00
|
|
|
<label for="name" class="form-label">{{ $t("Friendly Name") }}</label>
|
|
|
|
<input id="name" v-model="agent.name" type="text" class="form-control" optional>
|
2024-02-01 07:18:52 +00:00
|
|
|
</div>
|
|
|
|
|
2023-12-26 04:12:44 +08:00
|
|
|
<button type="submit" class="btn btn-primary" :disabled="connectingAgent">
|
|
|
|
<template v-if="connectingAgent">{{ $t("connecting") }}</template>
|
|
|
|
<template v-else>{{ $t("connect") }}</template>
|
|
|
|
</button>
|
|
|
|
</form>
|
2023-11-11 22:18:37 +08:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</transition>
|
|
|
|
<router-view ref="child" />
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<script>
|
2023-12-26 04:12:44 +08:00
|
|
|
import { statusNameShort } from "../../../common/util-common";
|
2023-11-11 22:18:37 +08:00
|
|
|
|
|
|
|
export default {
|
|
|
|
components: {
|
|
|
|
|
|
|
|
},
|
|
|
|
props: {
|
|
|
|
calculatedHeight: {
|
|
|
|
type: Number,
|
|
|
|
default: 0
|
|
|
|
}
|
|
|
|
},
|
|
|
|
data() {
|
|
|
|
return {
|
|
|
|
page: 1,
|
|
|
|
perPage: 25,
|
|
|
|
initialPerPage: 25,
|
|
|
|
paginationConfig: {
|
|
|
|
hideCount: true,
|
|
|
|
chunksNavigation: "scroll",
|
|
|
|
},
|
|
|
|
importantHeartBeatListLength: 0,
|
|
|
|
displayedRecords: [],
|
|
|
|
dockerRunCommand: "",
|
2023-12-26 04:12:44 +08:00
|
|
|
showAgentForm: false,
|
|
|
|
showRemoveAgentDialog: {},
|
2024-08-06 16:01:14 +00:00
|
|
|
showEditAgentNameDialog: {},
|
2023-12-26 04:12:44 +08:00
|
|
|
connectingAgent: false,
|
|
|
|
agent: {
|
|
|
|
url: "http://",
|
|
|
|
username: "",
|
|
|
|
password: "",
|
2024-08-06 16:01:14 +00:00
|
|
|
name: "",
|
|
|
|
updatedName: "",
|
2023-12-26 04:12:44 +08:00
|
|
|
}
|
2023-11-11 22:18:37 +08:00
|
|
|
};
|
|
|
|
},
|
|
|
|
|
|
|
|
computed: {
|
|
|
|
activeNum() {
|
|
|
|
return this.getStatusNum("active");
|
|
|
|
},
|
|
|
|
inactiveNum() {
|
|
|
|
return this.getStatusNum("inactive");
|
|
|
|
},
|
|
|
|
exitedNum() {
|
|
|
|
return this.getStatusNum("exited");
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
watch: {
|
|
|
|
perPage() {
|
|
|
|
this.$nextTick(() => {
|
|
|
|
this.getImportantHeartbeatListPaged();
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
page() {
|
|
|
|
this.getImportantHeartbeatListPaged();
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
mounted() {
|
|
|
|
this.initialPerPage = this.perPage;
|
|
|
|
|
|
|
|
window.addEventListener("resize", this.updatePerPage);
|
|
|
|
this.updatePerPage();
|
|
|
|
},
|
|
|
|
|
|
|
|
beforeUnmount() {
|
|
|
|
window.removeEventListener("resize", this.updatePerPage);
|
|
|
|
},
|
|
|
|
|
|
|
|
methods: {
|
|
|
|
|
2023-12-26 04:12:44 +08:00
|
|
|
addAgent() {
|
|
|
|
this.connectingAgent = true;
|
|
|
|
this.$root.getSocket().emit("addAgent", this.agent, (res) => {
|
|
|
|
this.$root.toastRes(res);
|
|
|
|
|
|
|
|
if (res.ok) {
|
|
|
|
this.showAgentForm = false;
|
|
|
|
this.agent = {
|
|
|
|
url: "http://",
|
|
|
|
username: "",
|
|
|
|
password: "",
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
this.connectingAgent = false;
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
removeAgent(url) {
|
|
|
|
this.$root.getSocket().emit("removeAgent", url, (res) => {
|
|
|
|
if (res.ok) {
|
|
|
|
this.$root.toastRes(res);
|
|
|
|
|
|
|
|
let urlObj = new URL(url);
|
|
|
|
let endpoint = urlObj.host;
|
|
|
|
|
|
|
|
// Remove the stack list and status list of the removed agent
|
|
|
|
delete this.$root.allAgentStackList[endpoint];
|
|
|
|
}
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2024-08-06 16:01:14 +00:00
|
|
|
updateName(url, updatedName) {
|
|
|
|
this.$root.getSocket().emit("updateAgent", url, updatedName, (res) => {
|
2024-02-01 18:54:52 +00:00
|
|
|
this.$root.toastRes(res);
|
|
|
|
|
|
|
|
if (res.ok) {
|
|
|
|
this.showAgentForm = false;
|
|
|
|
this.agent = {
|
2024-08-06 16:01:14 +00:00
|
|
|
updatedName: "",
|
2024-02-01 18:54:52 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2023-11-11 22:18:37 +08:00
|
|
|
getStatusNum(statusName) {
|
|
|
|
let num = 0;
|
|
|
|
|
2023-12-26 04:12:44 +08:00
|
|
|
for (let stackName in this.$root.completeStackList) {
|
|
|
|
const stack = this.$root.completeStackList[stackName];
|
2023-11-11 22:18:37 +08:00
|
|
|
if (statusNameShort(stack.status) === statusName) {
|
|
|
|
num += 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return num;
|
|
|
|
},
|
|
|
|
|
|
|
|
convertDockerRun() {
|
|
|
|
if (this.dockerRunCommand.trim() === "docker run") {
|
|
|
|
throw new Error("Please enter a docker run command");
|
|
|
|
}
|
|
|
|
|
|
|
|
// composerize is working in dev, but after "vite build", it is not working
|
|
|
|
// So pass to backend to do the conversion
|
|
|
|
this.$root.getSocket().emit("composerize", this.dockerRunCommand, (res) => {
|
|
|
|
if (res.ok) {
|
|
|
|
this.$root.composeTemplate = res.composeTemplate;
|
|
|
|
this.$router.push("/compose");
|
|
|
|
} else {
|
|
|
|
this.$root.toastRes(res);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Updates the displayed records when a new important heartbeat arrives.
|
|
|
|
* @param {object} heartbeat - The heartbeat object received.
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
onNewImportantHeartbeat(heartbeat) {
|
|
|
|
if (this.page === 1) {
|
|
|
|
this.displayedRecords.unshift(heartbeat);
|
|
|
|
if (this.displayedRecords.length > this.perPage) {
|
|
|
|
this.displayedRecords.pop();
|
|
|
|
}
|
|
|
|
this.importantHeartBeatListLength += 1;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Retrieves the length of the important heartbeat list for all monitors.
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
getImportantHeartbeatListLength() {
|
|
|
|
this.$root.getSocket().emit("monitorImportantHeartbeatListCount", null, (res) => {
|
|
|
|
if (res.ok) {
|
|
|
|
this.importantHeartBeatListLength = res.count;
|
|
|
|
this.getImportantHeartbeatListPaged();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Retrieves the important heartbeat list for the current page.
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
getImportantHeartbeatListPaged() {
|
|
|
|
const offset = (this.page - 1) * this.perPage;
|
|
|
|
this.$root.getSocket().emit("monitorImportantHeartbeatListPaged", null, offset, this.perPage, (res) => {
|
|
|
|
if (res.ok) {
|
|
|
|
this.displayedRecords = res.data;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Updates the number of items shown per page based on the available height.
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
updatePerPage() {
|
|
|
|
const tableContainer = this.$refs.tableContainer;
|
|
|
|
const tableContainerHeight = tableContainer.offsetHeight;
|
|
|
|
const availableHeight = window.innerHeight - tableContainerHeight;
|
|
|
|
const additionalPerPage = Math.floor(availableHeight / 58);
|
|
|
|
|
|
|
|
if (additionalPerPage > 0) {
|
|
|
|
this.perPage = Math.max(this.initialPerPage, this.perPage + additionalPerPage);
|
|
|
|
} else {
|
|
|
|
this.perPage = this.initialPerPage;
|
|
|
|
}
|
|
|
|
|
|
|
|
},
|
2024-02-01 18:54:52 +00:00
|
|
|
}
|
2023-11-11 22:18:37 +08:00
|
|
|
};
|
|
|
|
</script>
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
@import "../styles/vars";
|
|
|
|
|
|
|
|
.num {
|
|
|
|
font-size: 30px;
|
|
|
|
|
|
|
|
font-weight: bold;
|
|
|
|
display: block;
|
|
|
|
|
|
|
|
&.active {
|
|
|
|
color: $primary;
|
|
|
|
}
|
|
|
|
|
|
|
|
&.exited {
|
|
|
|
color: $danger;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.shadow-box {
|
|
|
|
padding: 20px;
|
|
|
|
}
|
|
|
|
|
|
|
|
table {
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
|
|
tr {
|
|
|
|
transition: all ease-in-out 0.2ms;
|
|
|
|
}
|
|
|
|
|
|
|
|
@media (max-width: 550px) {
|
|
|
|
table-layout: fixed;
|
|
|
|
overflow-wrap: break-word;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.docker-run {
|
|
|
|
background-color: $dark-bg !important;
|
|
|
|
border: none;
|
2023-11-12 11:22:31 +03:30
|
|
|
font-family: 'JetBrains Mono', monospace;
|
|
|
|
font-size: 15px;
|
2023-11-11 22:18:37 +08:00
|
|
|
}
|
2023-12-26 04:12:44 +08:00
|
|
|
|
|
|
|
.first-row .shadow-box {
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
.remove-agent {
|
|
|
|
cursor: pointer;
|
|
|
|
color: rgba(255, 255, 255, 0.3);
|
|
|
|
}
|
|
|
|
|
|
|
|
.agent {
|
|
|
|
a {
|
|
|
|
text-decoration: none;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-11-11 22:18:37 +08:00
|
|
|
</style>
|