Merge remote-tracking branch 'origin/master' into weblate-uptime-kuma-uptime-kuma

# Conflicts:
#	src/lang/de-DE.json
This commit is contained in:
Louis Lam
2023-06-11 14:35:10 +08:00
13 changed files with 516 additions and 57 deletions

View File

@@ -19,43 +19,18 @@
{{ $t("No Monitors, please") }} <router-link to="/add">{{ $t("add one") }}</router-link>
</div>
<router-link v-for="(item, index) in sortedMonitorList" :key="index" :to="monitorURL(item.id)" class="item" :class="{ 'disabled': ! item.active }" :title="item.description">
<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>
<MonitorListItem v-for="(item, index) in sortedMonitorList" :key="index" :monitor="item" :isSearch="searchText !== ''" />
</div>
</div>
</template>
<script>
import HeartbeatBar from "../components/HeartbeatBar.vue";
import Tag from "../components/Tag.vue";
import Uptime from "../components/Uptime.vue";
import MonitorListItem from "../components/MonitorListItem.vue";
import { getMonitorRelativeURL } from "../util.ts";
export default {
components: {
Uptime,
HeartbeatBar,
Tag,
MonitorListItem,
},
props: {
/** Should the scrollbar be shown */
@@ -91,6 +66,20 @@ export default {
sortedMonitorList() {
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);
}
// Filter result by active state, weight and alphabetical
result.sort((m1, m2) => {
if (m1.active !== m2.active) {
@@ -116,17 +105,6 @@ export default {
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;
},
},

View File

@@ -0,0 +1,204 @@
<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,
},
/** If the user is currently searching */
isSearch: {
type: Boolean,
default: false,
},
/** How many ancestors are above this monitor */
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() {
// Always unfold if monitor is accessed directly
if (this.monitor.childrenIDs.includes(parseInt(this.$route.params.id))) {
this.isCollapsed = false;
return;
}
// Set collapsed value based on local storage
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: {
/**
* Changes the collapsed value of the current monitor and saves it to local storage
*/
changeCollapsed() {
this.isCollapsed = !this.isCollapsed;
// Save collapsed value into local storage
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);
},
},
};
</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>

View File

@@ -776,5 +776,7 @@
"Monitor Setting": "{0}'s Monitor Einstellung",
"Badge Prefix": "Badge Präfix",
"Badge Suffix": "Badge Suffix",
"Badge Warn Days": "Badge Warnung Tage"
"Badge Warn Days": "Badge Warnung Tage",
"Group": "Gruppe",
"Monitor Group": "Monitor Gruppe"
}

View File

@@ -744,5 +744,7 @@
"Badge Down Days": "Badge Down Days",
"Badge Style": "Badge Style",
"Badge value (For Testing only.)": "Badge value (For Testing only.)",
"Badge URL": "Badge URL"
"Badge URL": "Badge URL",
"Group": "Group",
"Monitor Group": "Monitor Group"
}

View File

@@ -1,6 +1,7 @@
<template>
<transition name="slide-fade" appear>
<div v-if="monitor">
<router-link v-if="group !== ''" :to="monitorURL(monitor.parent)"> {{ group }}</router-link>
<h1> {{ monitor.name }}</h1>
<p v-if="monitor.description">{{ monitor.description }}</p>
<div class="tags">
@@ -40,7 +41,7 @@
<button v-if="monitor.active" class="btn btn-normal" @click="pauseDialog">
<font-awesome-icon icon="pause" /> {{ $t("Pause") }}
</button>
<button v-if="! monitor.active" class="btn btn-primary" @click="resumeMonitor">
<button v-if="! monitor.active" class="btn btn-primary" :disabled="monitor.forceInactive" @click="resumeMonitor">
<font-awesome-icon icon="play" /> {{ $t("Resume") }}
</button>
<router-link :to=" '/edit/' + monitor.id " class="btn btn-normal">
@@ -69,7 +70,7 @@
<div class="shadow-box big-padding text-center stats">
<div class="row">
<div class="col-12 col-sm col row d-flex align-items-center d-sm-block">
<div v-if="monitor.type !== 'group'" class="col-12 col-sm col row d-flex align-items-center d-sm-block">
<h4 class="col-4 col-sm-12">{{ pingTitle() }}</h4>
<p class="col-4 col-sm-12 mb-0 mb-sm-2">({{ $t("Current") }})</p>
<span class="col-4 col-sm-12 num">
@@ -78,7 +79,7 @@
</a>
</span>
</div>
<div class="col-12 col-sm col row d-flex align-items-center d-sm-block">
<div v-if="monitor.type !== 'group'" class="col-12 col-sm col row d-flex align-items-center d-sm-block">
<h4 class="col-4 col-sm-12">{{ pingTitle(true) }}</h4>
<p class="col-4 col-sm-12 mb-0 mb-sm-2">(24{{ $t("-hour") }})</p>
<span class="col-4 col-sm-12 num">
@@ -214,6 +215,7 @@ import Pagination from "v-pagination-3";
const PingChart = defineAsyncComponent(() => import("../components/PingChart.vue"));
import Tag from "../components/Tag.vue";
import CertificateInfo from "../components/CertificateInfo.vue";
import { getMonitorRelativeURL } from "../util.ts";
import { URL } from "whatwg-url";
export default {
@@ -313,6 +315,13 @@ export default {
return this.heartBeatList.slice(startIndex, endIndex);
},
group() {
if (!this.monitor.pathName.includes("/")) {
return "";
}
return this.monitor.pathName.substr(0, this.monitor.pathName.lastIndexOf("/"));
},
pushURL() {
return this.$root.baseURL + "/api/push/" + this.monitor.pushToken + "?status=up&msg=OK&ping=";
},
@@ -409,6 +418,15 @@ export default {
return this.$t(translationPrefix + "Ping");
},
/**
* Get URL of monitor
* @param {number} id ID of monitor
* @returns {string} Relative URL of monitor
*/
monitorURL(id) {
return getMonitorRelativeURL(id);
},
/** Filter and hide password in URL for display */
filterPassword(urlString) {
try {

View File

@@ -36,7 +36,7 @@
v-model="affectedMonitors"
:options="affectedMonitorsOptions"
track-by="id"
label="name"
label="pathName"
:multiple="true"
:close-on-select="false"
:clear-on-select="false"
@@ -381,17 +381,39 @@ export default {
},
},
mounted() {
this.init();
this.$root.getMonitorList((res) => {
if (res.ok) {
Object.values(this.$root.monitorList).map(monitor => {
Object.values(this.$root.monitorList).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);
}).map(monitor => {
this.affectedMonitorsOptions.push({
id: monitor.id,
name: monitor.name,
pathName: monitor.pathName,
});
});
}
this.init();
});
},
methods: {
@@ -429,7 +451,7 @@ export default {
this.$root.getSocket().emit("getMonitorMaintenance", this.$route.params.id, (res) => {
if (res.ok) {
Object.values(res.monitors).map(monitor => {
this.affectedMonitors.push(monitor);
this.affectedMonitors.push(this.affectedMonitorsOptions.find(item => item.id === monitor.id));
});
} else {
toast.error(res.msg);

View File

@@ -12,6 +12,9 @@
<label for="type" class="form-label">{{ $t("Monitor Type") }}</label>
<select id="type" v-model="monitor.type" class="form-select">
<optgroup :label="$t('General Monitor Type')">
<option value="group">
{{ $t("Group") }}
</option>
<option value="http">
HTTP(s)
</option>
@@ -89,6 +92,15 @@
<input id="name" v-model="monitor.name" type="text" class="form-control" required>
</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 -->
<div v-if="monitor.type === 'http' || monitor.type === 'keyword' || monitor.type === 'browser' " class="my-3">
<label for="url" class="form-label">{{ $t("URL") }}</label>
@@ -807,6 +819,48 @@ message HealthCheckResponse {
return null;
},
// Filter result by active state, weight and alphabetical
// Only return groups which arent't itself and one of its decendants
sortedMonitorList() {
let result = Object.values(this.$root.monitorList);
console.log(this.monitor.childrenIDs);
// Only groups, not itself, not a decendant
result = result.filter(
monitor => monitor.type === "group" &&
monitor.id !== this.monitor.id &&
!this.monitor.childrenIDs?.includes(monitor.id)
);
// Filter result by active state, weight and alphabetical
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: {
"$root.proxyList"() {
@@ -926,6 +980,7 @@ message HealthCheckResponse {
this.monitor = {
type: "http",
name: "",
parent: null,
url: "https://",
method: "GET",
interval: 60,

View File

@@ -278,11 +278,11 @@
</div>
<div class="mt-3">
<div v-if="allMonitorList.length > 0 && loadedData">
<div v-if="sortedMonitorList.length > 0 && loadedData">
<label>{{ $t("Add a monitor") }}:</label>
<VueMultiselect
v-model="selectedMonitor"
:options="allMonitorList"
:options="sortedMonitorList"
:multiple="false"
:searchable="true"
:placeholder="$t('Add a monitor')"
@@ -292,7 +292,7 @@
>
<template #option="{ option }">
<div class="d-inline-flex">
<span>{{ option.name }} <Tag v-for="tag in option.tags" :key="tag" :item="tag" :size="'sm'" /></span>
<span>{{ option.pathName }} <Tag v-for="tag in option.tags" :key="tag" :item="tag" :size="'sm'" /></span>
</div>
</template>
</VueMultiselect>
@@ -449,7 +449,7 @@ export default {
/**
* If the monitor is added to public list, which will not be in this list.
*/
allMonitorList() {
sortedMonitorList() {
let result = [];
for (let id in this.$root.monitorList) {
@@ -459,6 +459,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;
},