mirror of
https://github.com/louislam/uptime-kuma.git
synced 2024-11-23 23:04:04 +00:00
feat: add ability to group monitors in dashboard
This commit is contained in:
parent
d99d37898e
commit
645fd94bba
11 changed files with 411 additions and 44 deletions
6
db/patch-add-parent-monitor.sql
Normal file
6
db/patch-add-parent-monitor.sql
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
BEGIN TRANSACTION;
|
||||||
|
|
||||||
|
ALTER TABLE monitor
|
||||||
|
ADD parent INTEGER REFERENCES [monitor] ([id]) ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
COMMIT
|
|
@ -68,6 +68,7 @@ class Database {
|
||||||
"patch-ping-packet-size.sql": true,
|
"patch-ping-packet-size.sql": true,
|
||||||
"patch-maintenance-table2.sql": true,
|
"patch-maintenance-table2.sql": true,
|
||||||
"patch-add-gamedig-monitor.sql": true,
|
"patch-add-gamedig-monitor.sql": true,
|
||||||
|
"patch-add-parent-monitor.sql": true,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -72,6 +72,9 @@ class Monitor extends BeanModel {
|
||||||
let data = {
|
let data = {
|
||||||
id: this.id,
|
id: this.id,
|
||||||
name: this.name,
|
name: this.name,
|
||||||
|
pathName: await this.getPathName(),
|
||||||
|
parent: this.parent,
|
||||||
|
childrenIDs: await Monitor.getAllChildrenIDs(this.id),
|
||||||
url: this.url,
|
url: this.url,
|
||||||
method: this.method,
|
method: this.method,
|
||||||
hostname: this.hostname,
|
hostname: this.hostname,
|
||||||
|
@ -253,6 +256,25 @@ class Monitor extends BeanModel {
|
||||||
if (await Monitor.isUnderMaintenance(this.id)) {
|
if (await Monitor.isUnderMaintenance(this.id)) {
|
||||||
bean.msg = "Monitor under maintenance";
|
bean.msg = "Monitor under maintenance";
|
||||||
bean.status = MAINTENANCE;
|
bean.status = MAINTENANCE;
|
||||||
|
} else if (this.type === "group") {
|
||||||
|
const children = await Monitor.getChildren(this.id);
|
||||||
|
|
||||||
|
bean.status = UP;
|
||||||
|
bean.msg = "All childs up and running";
|
||||||
|
for (const child of children) {
|
||||||
|
const lastBeat = await Monitor.getPreviousHeartbeat(child.id);
|
||||||
|
|
||||||
|
// Only change state if the monitor is in worse conditions then the ones before
|
||||||
|
if (bean.status === UP && (lastBeat.status === PENDING || lastBeat.status === DOWN)) {
|
||||||
|
bean.status = lastBeat.status;
|
||||||
|
} else if (bean.status === PENDING && lastBeat.status === DOWN) {
|
||||||
|
bean.status = lastBeat.status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bean.status !== UP) {
|
||||||
|
bean.msg = "Child inaccessible";
|
||||||
|
}
|
||||||
} else if (this.type === "http" || this.type === "keyword") {
|
} else if (this.type === "http" || this.type === "keyword") {
|
||||||
// Do not do any queries/high loading things before the "bean.ping"
|
// Do not do any queries/high loading things before the "bean.ping"
|
||||||
let startTime = dayjs().valueOf();
|
let startTime = dayjs().valueOf();
|
||||||
|
@ -1283,6 +1305,77 @@ class Monitor extends BeanModel {
|
||||||
throw new Error(`Interval cannot be less than ${MIN_INTERVAL_SECOND} seconds`);
|
throw new Error(`Interval cannot be less than ${MIN_INTERVAL_SECOND} seconds`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets Parent of the monitor
|
||||||
|
* @param {number} monitorID ID of monitor to get
|
||||||
|
* @returns {Promise<LooseObject<any>>}
|
||||||
|
*/
|
||||||
|
static async getParent(monitorID) {
|
||||||
|
return await R.getRow(`
|
||||||
|
SELECT parent.* FROM monitor parent
|
||||||
|
LEFT JOIN monitor child
|
||||||
|
ON child.parent = parent.id
|
||||||
|
WHERE child.id = ?
|
||||||
|
`, [
|
||||||
|
monitorID,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all Children of the monitor
|
||||||
|
* @param {number} monitorID ID of monitor to get
|
||||||
|
* @returns {Promise<LooseObject<any>>}
|
||||||
|
*/
|
||||||
|
static async getChildren(monitorID) {
|
||||||
|
return await R.getAll(`
|
||||||
|
SELECT * FROM monitor
|
||||||
|
WHERE parent = ?
|
||||||
|
`, [
|
||||||
|
monitorID,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets Full Path-Name (Groups and Name)
|
||||||
|
* @returns {Promise<String>}
|
||||||
|
*/
|
||||||
|
async getPathName() {
|
||||||
|
let path = this.name;
|
||||||
|
|
||||||
|
if (this.parent === null) {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
let parent = await Monitor.getParent(this.id);
|
||||||
|
while (parent !== null) {
|
||||||
|
path = `${parent.name} / ${path}`;
|
||||||
|
parent = await Monitor.getParent(parent.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets recursive all child ids
|
||||||
|
* @returns {Promise<Array>}
|
||||||
|
*/
|
||||||
|
static async getAllChildrenIDs(monitorID) {
|
||||||
|
const childs = await Monitor.getChildren(monitorID);
|
||||||
|
|
||||||
|
if (childs === null) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
let childrenIDs = [];
|
||||||
|
|
||||||
|
for (const child of childs) {
|
||||||
|
childrenIDs.push(child.id);
|
||||||
|
childrenIDs = childrenIDs.concat(await Monitor.getAllChildrenIDs(child.id));
|
||||||
|
}
|
||||||
|
|
||||||
|
return childrenIDs;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Monitor;
|
module.exports = Monitor;
|
||||||
|
|
|
@ -678,6 +678,7 @@ let needSetup = false;
|
||||||
server.monitorList[monitor.id]?.prometheus()?.remove();
|
server.monitorList[monitor.id]?.prometheus()?.remove();
|
||||||
|
|
||||||
bean.name = monitor.name;
|
bean.name = monitor.name;
|
||||||
|
bean.parent = monitor.parent;
|
||||||
bean.type = monitor.type;
|
bean.type = monitor.type;
|
||||||
bean.url = monitor.url;
|
bean.url = monitor.url;
|
||||||
bean.method = monitor.method;
|
bean.method = monitor.method;
|
||||||
|
|
|
@ -19,43 +19,18 @@
|
||||||
{{ $t("No Monitors, please") }} <router-link to="/add">{{ $t("add one") }}</router-link>
|
{{ $t("No Monitors, please") }} <router-link to="/add">{{ $t("add one") }}</router-link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<router-link v-for="(item, index) in sortedMonitorList" :key="index" :to="monitorURL(item.id)" class="item" :class="{ 'disabled': ! item.active }">
|
<MonitorListItem v-for="(item, index) in sortedMonitorList" :key="index" :monitor="item" :isSearch="searchText !== ''" />
|
||||||
<div class="row">
|
|
||||||
<div class="col-9 col-md-8 small-padding" :class="{ 'monitor-item': $root.userHeartbeatBar == 'bottom' || $root.userHeartbeatBar == 'none' }">
|
|
||||||
<div class="info">
|
|
||||||
<Uptime :monitor="item" type="24" :pill="true" />
|
|
||||||
{{ item.name }}
|
|
||||||
</div>
|
|
||||||
<div class="tags">
|
|
||||||
<Tag v-for="tag in item.tags" :key="tag" :item="tag" :size="'sm'" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-show="$root.userHeartbeatBar == 'normal'" :key="$root.userHeartbeatBar" class="col-3 col-md-4">
|
|
||||||
<HeartbeatBar size="small" :monitor-id="item.id" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="$root.userHeartbeatBar == 'bottom'" class="row">
|
|
||||||
<div class="col-12 bottom-style">
|
|
||||||
<HeartbeatBar size="small" :monitor-id="item.id" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</router-link>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import HeartbeatBar from "../components/HeartbeatBar.vue";
|
import MonitorListItem from "../components/MonitorListItem.vue";
|
||||||
import Tag from "../components/Tag.vue";
|
|
||||||
import Uptime from "../components/Uptime.vue";
|
|
||||||
import { getMonitorRelativeURL } from "../util.ts";
|
import { getMonitorRelativeURL } from "../util.ts";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
Uptime,
|
MonitorListItem,
|
||||||
HeartbeatBar,
|
|
||||||
Tag,
|
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
/** Should the scrollbar be shown */
|
/** Should the scrollbar be shown */
|
||||||
|
@ -91,6 +66,19 @@ export default {
|
||||||
sortedMonitorList() {
|
sortedMonitorList() {
|
||||||
let result = Object.values(this.$root.monitorList);
|
let result = Object.values(this.$root.monitorList);
|
||||||
|
|
||||||
|
// Simple filter by search text
|
||||||
|
// finds monitor name, tag name or tag value
|
||||||
|
if (this.searchText !== "") {
|
||||||
|
const loweredSearchText = this.searchText.toLowerCase();
|
||||||
|
result = result.filter(monitor => {
|
||||||
|
return monitor.name.toLowerCase().includes(loweredSearchText)
|
||||||
|
|| monitor.tags.find(tag => tag.name.toLowerCase().includes(loweredSearchText)
|
||||||
|
|| tag.value?.toLowerCase().includes(loweredSearchText));
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
result = result.filter(monitor => monitor.parent === null);
|
||||||
|
}
|
||||||
|
|
||||||
result.sort((m1, m2) => {
|
result.sort((m1, m2) => {
|
||||||
|
|
||||||
if (m1.active !== m2.active) {
|
if (m1.active !== m2.active) {
|
||||||
|
@ -116,17 +104,6 @@ export default {
|
||||||
return m1.name.localeCompare(m2.name);
|
return m1.name.localeCompare(m2.name);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Simple filter by search text
|
|
||||||
// finds monitor name, tag name or tag value
|
|
||||||
if (this.searchText !== "") {
|
|
||||||
const loweredSearchText = this.searchText.toLowerCase();
|
|
||||||
result = result.filter(monitor => {
|
|
||||||
return monitor.name.toLowerCase().includes(loweredSearchText)
|
|
||||||
|| monitor.tags.find(tag => tag.name.toLowerCase().includes(loweredSearchText)
|
|
||||||
|| tag.value?.toLowerCase().includes(loweredSearchText));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
196
src/components/MonitorListItem.vue
Normal file
196
src/components/MonitorListItem.vue
Normal file
|
@ -0,0 +1,196 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<router-link :to="monitorURL(monitor.id)" class="item" :class="{ 'disabled': ! monitor.active }">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-9 col-md-8 small-padding" :class="{ 'monitor-item': $root.userHeartbeatBar == 'bottom' || $root.userHeartbeatBar == 'none' }">
|
||||||
|
<div class="info" :style="depthMargin">
|
||||||
|
<Uptime :monitor="monitor" type="24" :pill="true" />
|
||||||
|
<span v-if="hasChildren" class="collapse-padding" @click.prevent="changeCollapsed">
|
||||||
|
<font-awesome-icon icon="chevron-down" class="animated" :class="{ collapsed: isCollapsed}" />
|
||||||
|
</span>
|
||||||
|
{{ monitorName }}
|
||||||
|
</div>
|
||||||
|
<div class="tags">
|
||||||
|
<Tag v-for="tag in monitor.tags" :key="tag" :item="tag" :size="'sm'" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-show="$root.userHeartbeatBar == 'normal'" :key="$root.userHeartbeatBar" class="col-3 col-md-4">
|
||||||
|
<HeartbeatBar size="small" :monitor-id="monitor.id" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="$root.userHeartbeatBar == 'bottom'" class="row">
|
||||||
|
<div class="col-12 bottom-style">
|
||||||
|
<HeartbeatBar size="small" :monitor-id="monitor.id" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</router-link>
|
||||||
|
|
||||||
|
<transition name="slide-fade-up">
|
||||||
|
<div v-if="!isCollapsed" class="childs">
|
||||||
|
<MonitorListItem v-for="(item, index) in sortedChildMonitorList" :key="index" :monitor="item" :isSearch="isSearch" :depth="depth + 1" />
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import HeartbeatBar from "../components/HeartbeatBar.vue";
|
||||||
|
import Tag from "../components/Tag.vue";
|
||||||
|
import Uptime from "../components/Uptime.vue";
|
||||||
|
import { getMonitorRelativeURL } from "../util.ts";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "MonitorListItem",
|
||||||
|
components: {
|
||||||
|
Uptime,
|
||||||
|
HeartbeatBar,
|
||||||
|
Tag,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
/** Monitor this represents */
|
||||||
|
monitor: {
|
||||||
|
type: Object,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
isSearch: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
depth: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isCollapsed: true,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
sortedChildMonitorList() {
|
||||||
|
let result = Object.values(this.$root.monitorList);
|
||||||
|
|
||||||
|
result = result.filter(childMonitor => childMonitor.parent === this.monitor.id);
|
||||||
|
|
||||||
|
result.sort((m1, m2) => {
|
||||||
|
|
||||||
|
if (m1.active !== m2.active) {
|
||||||
|
if (m1.active === 0) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m2.active === 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m1.weight !== m2.weight) {
|
||||||
|
if (m1.weight > m2.weight) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m1.weight < m2.weight) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return m1.name.localeCompare(m2.name);
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
hasChildren() {
|
||||||
|
return this.sortedChildMonitorList.length > 0;
|
||||||
|
},
|
||||||
|
depthMargin() {
|
||||||
|
return {
|
||||||
|
marginLeft: `${31 * this.depth}px`,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
monitorName() {
|
||||||
|
if (this.isSearch) {
|
||||||
|
return this.monitor.pathName;
|
||||||
|
} else {
|
||||||
|
return this.monitor.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
beforeMount() {
|
||||||
|
// this.isCollapsed = localStorage.getItem(`monitor_${this.monitor.id}_collapsed`) === "true";
|
||||||
|
|
||||||
|
let storage = window.localStorage.getItem("monitorCollapsed");
|
||||||
|
if (storage === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let storageObject = JSON.parse(storage);
|
||||||
|
if (storageObject[`monitor_${this.monitor.id}`] === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isCollapsed = storageObject[`monitor_${this.monitor.id}`];
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
changeCollapsed() {
|
||||||
|
this.isCollapsed = !this.isCollapsed;
|
||||||
|
|
||||||
|
let storage = window.localStorage.getItem("monitorCollapsed");
|
||||||
|
let storageObject = {};
|
||||||
|
if (storage !== null) {
|
||||||
|
storageObject = JSON.parse(storage);
|
||||||
|
}
|
||||||
|
storageObject[`monitor_${this.monitor.id}`] = this.isCollapsed;
|
||||||
|
|
||||||
|
window.localStorage.setItem("monitorCollapsed", JSON.stringify(storageObject));
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Get URL of monitor
|
||||||
|
* @param {number} id ID of monitor
|
||||||
|
* @returns {string} Relative URL of monitor
|
||||||
|
*/
|
||||||
|
monitorURL(id) {
|
||||||
|
return getMonitorRelativeURL(id);
|
||||||
|
},
|
||||||
|
// /** Clear the search bar */
|
||||||
|
// clearSearchText() {
|
||||||
|
// this.searchText = "";
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import "../assets/vars.scss";
|
||||||
|
|
||||||
|
.small-padding {
|
||||||
|
padding-left: 5px !important;
|
||||||
|
padding-right: 5px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapse-padding {
|
||||||
|
padding-left: 8px !important;
|
||||||
|
padding-right: 2px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
// .monitor-item {
|
||||||
|
// width: 100%;
|
||||||
|
// }
|
||||||
|
|
||||||
|
.tags {
|
||||||
|
margin-top: 4px;
|
||||||
|
padding-left: 67px;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapsed {
|
||||||
|
transform: rotate(-90deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.animated {
|
||||||
|
transition: all 0.2s $easing-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
|
@ -643,5 +643,7 @@
|
||||||
"Custom": "Benutzerdefiniert",
|
"Custom": "Benutzerdefiniert",
|
||||||
"Enable DNS Cache": "DNS Cache aktivieren",
|
"Enable DNS Cache": "DNS Cache aktivieren",
|
||||||
"Enable": "Aktivieren",
|
"Enable": "Aktivieren",
|
||||||
"Disable": "Deaktivieren"
|
"Disable": "Deaktivieren",
|
||||||
|
"Group": "Gruppe",
|
||||||
|
"Monitor Group": "Monitor Gruppe"
|
||||||
}
|
}
|
||||||
|
|
|
@ -682,5 +682,7 @@
|
||||||
"onebotUserOrGroupId": "Group/User ID",
|
"onebotUserOrGroupId": "Group/User ID",
|
||||||
"onebotSafetyTips": "For safety, must set access token",
|
"onebotSafetyTips": "For safety, must set access token",
|
||||||
"PushDeer Key": "PushDeer Key",
|
"PushDeer Key": "PushDeer Key",
|
||||||
"wayToGetClickSendSMSToken": "You can get API Username and API Key from {0} ."
|
"wayToGetClickSendSMSToken": "You can get API Username and API Key from {0} .",
|
||||||
|
"Group": "Group",
|
||||||
|
"Monitor Group": "Monitor Group"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<transition name="slide-fade" appear>
|
<transition name="slide-fade" appear>
|
||||||
<div v-if="monitor">
|
<div v-if="monitor">
|
||||||
|
<span> {{ group }}</span>
|
||||||
<h1> {{ monitor.name }}</h1>
|
<h1> {{ monitor.name }}</h1>
|
||||||
<div class="tags">
|
<div class="tags">
|
||||||
<Tag v-for="tag in monitor.tags" :key="tag.id" :item="tag" :size="'sm'" />
|
<Tag v-for="tag in monitor.tags" :key="tag.id" :item="tag" :size="'sm'" />
|
||||||
|
@ -286,6 +287,13 @@ export default {
|
||||||
const endIndex = startIndex + this.perPage;
|
const endIndex = startIndex + this.perPage;
|
||||||
return this.heartBeatList.slice(startIndex, endIndex);
|
return this.heartBeatList.slice(startIndex, endIndex);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
group() {
|
||||||
|
if (!this.monitor.pathName.includes("/")) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return this.monitor.pathName.substr(0, this.monitor.pathName.lastIndexOf("/"));
|
||||||
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,9 @@
|
||||||
<label for="type" class="form-label">{{ $t("Monitor Type") }}</label>
|
<label for="type" class="form-label">{{ $t("Monitor Type") }}</label>
|
||||||
<select id="type" v-model="monitor.type" class="form-select">
|
<select id="type" v-model="monitor.type" class="form-select">
|
||||||
<optgroup :label="$t('General Monitor Type')">
|
<optgroup :label="$t('General Monitor Type')">
|
||||||
|
<option value="group">
|
||||||
|
{{ $t("Group") }}
|
||||||
|
</option>
|
||||||
<option value="http">
|
<option value="http">
|
||||||
HTTP(s)
|
HTTP(s)
|
||||||
</option>
|
</option>
|
||||||
|
@ -79,6 +82,15 @@
|
||||||
<input id="name" v-model="monitor.name" type="text" class="form-control" required>
|
<input id="name" v-model="monitor.name" type="text" class="form-control" required>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Parent Monitor -->
|
||||||
|
<div class="my-3">
|
||||||
|
<label for="parent" class="form-label">{{ $t("Monitor Group") }}</label>
|
||||||
|
<select v-model="monitor.parent" class="form-select" :disabled="sortedMonitorList.length === 0">
|
||||||
|
<option :value="null" selected>{{ $t("None") }}</option>
|
||||||
|
<option v-for="parentMonitor in sortedMonitorList" :key="parentMonitor.id" :value="parentMonitor.id">{{ parentMonitor.pathName }}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- URL -->
|
<!-- URL -->
|
||||||
<div v-if="monitor.type === 'http' || monitor.type === 'keyword' " class="my-3">
|
<div v-if="monitor.type === 'http' || monitor.type === 'keyword' " class="my-3">
|
||||||
<label for="url" class="form-label">{{ $t("URL") }}</label>
|
<label for="url" class="form-label">{{ $t("URL") }}</label>
|
||||||
|
@ -737,6 +749,49 @@ message HealthCheckResponse {
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
sortedMonitorList() {
|
||||||
|
// return Object.values(this.$root.monitorList).filter(monitor => {
|
||||||
|
// // Only return monitors which aren't related to the current selected
|
||||||
|
// if (monitor.id === this.monitor.id || monitor.parent === this.monitor.id) {
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
// return true;
|
||||||
|
// });
|
||||||
|
|
||||||
|
let result = Object.values(this.$root.monitorList);
|
||||||
|
console.log(this.monitor.childrenIDs);
|
||||||
|
result = result.filter(monitor => monitor.type === "group");
|
||||||
|
result = result.filter(monitor => monitor.id !== this.monitor.id);
|
||||||
|
result = result.filter(monitor => !this.monitor.childrenIDs?.includes(monitor.id));
|
||||||
|
|
||||||
|
result.sort((m1, m2) => {
|
||||||
|
|
||||||
|
if (m1.active !== m2.active) {
|
||||||
|
if (m1.active === 0) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m2.active === 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m1.weight !== m2.weight) {
|
||||||
|
if (m1.weight > m2.weight) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m1.weight < m2.weight) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return m1.pathName.localeCompare(m2.pathName);
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
"$root.proxyList"() {
|
"$root.proxyList"() {
|
||||||
|
@ -839,6 +894,7 @@ message HealthCheckResponse {
|
||||||
this.monitor = {
|
this.monitor = {
|
||||||
type: "http",
|
type: "http",
|
||||||
name: "",
|
name: "",
|
||||||
|
parent: null,
|
||||||
url: "https://",
|
url: "https://",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
interval: 60,
|
interval: 60,
|
||||||
|
|
|
@ -254,10 +254,10 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-3">
|
<div class="mt-3">
|
||||||
<div v-if="allMonitorList.length > 0 && loadedData">
|
<div v-if="sortedMonitorList.length > 0 && loadedData">
|
||||||
<label>{{ $t("Add a monitor") }}:</label>
|
<label>{{ $t("Add a monitor") }}:</label>
|
||||||
<select v-model="selectedMonitor" class="form-control">
|
<select v-model="selectedMonitor" class="form-control">
|
||||||
<option v-for="monitor in allMonitorList" :key="monitor.id" :value="monitor">{{ monitor.name }}</option>
|
<option v-for="monitor in sortedMonitorList" :key="monitor.id" :value="monitor">{{ monitor.pathName }}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="text-center">
|
<div v-else class="text-center">
|
||||||
|
@ -391,7 +391,7 @@ export default {
|
||||||
/**
|
/**
|
||||||
* If the monitor is added to public list, which will not be in this list.
|
* If the monitor is added to public list, which will not be in this list.
|
||||||
*/
|
*/
|
||||||
allMonitorList() {
|
sortedMonitorList() {
|
||||||
let result = [];
|
let result = [];
|
||||||
|
|
||||||
for (let id in this.$root.monitorList) {
|
for (let id in this.$root.monitorList) {
|
||||||
|
@ -401,6 +401,31 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
result.sort((m1, m2) => {
|
||||||
|
|
||||||
|
if (m1.active !== m2.active) {
|
||||||
|
if (m1.active === 0) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m2.active === 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m1.weight !== m2.weight) {
|
||||||
|
if (m1.weight > m2.weight) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m1.weight < m2.weight) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return m1.pathName.localeCompare(m2.pathName);
|
||||||
|
});
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue