mirror of
https://github.com/louislam/uptime-kuma.git
synced 2024-11-27 16:54:04 +00:00
Feat: Toast notification timeout settings (#3441)
* Add toast timeout to the settings Changing gui, adding timeout with a fix value memo rc rollback readme cleanup code cleanup code Review fixes review fix 2 * Feat: Add clearAll button below toastContainer * Feat: Load & Apply defaults, improve wording Chore: Remove unused * Feat: Change setting to affect monitor notif. only * Apply suggestions from code review Co-authored-by: Matthew Nickson <mnickson@sidingsmedia.com> * Chore: Fix JSDoc --------- Co-authored-by: Berczi Sandor <sandor.berczi@urss.hu> Co-authored-by: Matthew Nickson <mnickson@sidingsmedia.com>
This commit is contained in:
parent
62f4434711
commit
bfc7b498be
7 changed files with 218 additions and 11 deletions
|
@ -609,6 +609,18 @@ $shadow-box-padding: 20px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 770px) {
|
||||||
|
.toast-container {
|
||||||
|
margin-bottom: 100px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 550px) {
|
||||||
|
.toast-container {
|
||||||
|
margin-bottom: 126px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Localization
|
// Localization
|
||||||
|
|
||||||
@import "localization.scss";
|
@import "localization.scss";
|
||||||
|
|
|
@ -20,6 +20,39 @@
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="my-4 pt-4">
|
||||||
|
<h5 class="my-4 settings-subheading">{{ $t("monitorToastMessagesLabel") }}</h5>
|
||||||
|
<p>{{ $t("monitorToastMessagesDescription") }}</p>
|
||||||
|
|
||||||
|
<div class="my-4">
|
||||||
|
<label for="toastErrorTimeoutSecs" class="form-label">
|
||||||
|
{{ $t("toastErrorTimeout") }}
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="toastErrorTimeoutSecs"
|
||||||
|
v-model="toastErrorTimeoutSecs"
|
||||||
|
type="number"
|
||||||
|
class="form-control"
|
||||||
|
min="-1"
|
||||||
|
step="1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="my-4">
|
||||||
|
<label for="toastSuccessTimeoutSecs" class="form-label">
|
||||||
|
{{ $t("toastSuccessTimeout") }}
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="toastSuccessTimeoutSecs"
|
||||||
|
v-model="toastSuccessTimeoutSecs"
|
||||||
|
type="number"
|
||||||
|
class="form-control"
|
||||||
|
min="-1"
|
||||||
|
step="1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="my-4 pt-4">
|
<div class="my-4 pt-4">
|
||||||
<h5 class="my-4 settings-subheading">{{ $t("settingsCertificateExpiry") }}</h5>
|
<h5 class="my-4 settings-subheading">{{ $t("settingsCertificateExpiry") }}</h5>
|
||||||
<p>{{ $t("certificationExpiryDescription") }}</p>
|
<p>{{ $t("certificationExpiryDescription") }}</p>
|
||||||
|
@ -58,6 +91,8 @@ export default {
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
toastSuccessTimeoutSecs: 20,
|
||||||
|
toastErrorTimeoutSecs: -1,
|
||||||
/**
|
/**
|
||||||
* Variable to store the input for new certificate expiry day.
|
* Variable to store the input for new certificate expiry day.
|
||||||
*/
|
*/
|
||||||
|
@ -77,6 +112,26 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
// Parse, store and apply new timeout settings.
|
||||||
|
toastSuccessTimeoutSecs(newTimeout) {
|
||||||
|
const parsedTimeout = parseInt(newTimeout);
|
||||||
|
if (parsedTimeout != null && !Number.isNaN(parsedTimeout)) {
|
||||||
|
localStorage.toastSuccessTimeout = newTimeout > 0 ? newTimeout * 1000 : newTimeout;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
toastErrorTimeoutSecs(newTimeout) {
|
||||||
|
const parsedTimeout = parseInt(newTimeout);
|
||||||
|
if (parsedTimeout != null && !Number.isNaN(parsedTimeout)) {
|
||||||
|
localStorage.toastErrorTimeout = newTimeout > 0 ? newTimeout * 1000 : newTimeout;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.loadToastTimeoutSettings();
|
||||||
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
/**
|
/**
|
||||||
* Remove a day from expiry notification days.
|
* Remove a day from expiry notification days.
|
||||||
|
@ -108,6 +163,27 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads toast timeout settings from storage to component data.
|
||||||
|
*/
|
||||||
|
loadToastTimeoutSettings() {
|
||||||
|
const successTimeout = localStorage.toastSuccessTimeout;
|
||||||
|
if (successTimeout !== undefined) {
|
||||||
|
const parsedTimeout = parseInt(successTimeout);
|
||||||
|
if (parsedTimeout != null && !Number.isNaN(parsedTimeout)) {
|
||||||
|
this.toastSuccessTimeoutSecs = parsedTimeout > 0 ? parsedTimeout / 1000 : parsedTimeout;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const errorTimeout = localStorage.toastErrorTimeout;
|
||||||
|
if (errorTimeout !== undefined) {
|
||||||
|
const parsedTimeout = parseInt(errorTimeout);
|
||||||
|
if (parsedTimeout != null && !Number.isNaN(parsedTimeout)) {
|
||||||
|
this.toastErrorTimeoutSecs = parsedTimeout > 0 ? parsedTimeout / 1000 : parsedTimeout;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -785,6 +785,10 @@
|
||||||
"Badge URL": "Badge URL",
|
"Badge URL": "Badge URL",
|
||||||
"Group": "Group",
|
"Group": "Group",
|
||||||
"Monitor Group": "Monitor Group",
|
"Monitor Group": "Monitor Group",
|
||||||
|
"monitorToastMessagesLabel": "Monitor Toast notifications",
|
||||||
|
"monitorToastMessagesDescription": "Toast notifications for monitors disappear after given time in seconds. Set to -1 disables timeout. Set to 0 disables toast notifications.",
|
||||||
|
"toastErrorTimeout": "Timeout for Error Notifications",
|
||||||
|
"toastSuccessTimeout": "Timeout for Success Notifications",
|
||||||
"Kafka Brokers": "Kafka Brokers",
|
"Kafka Brokers": "Kafka Brokers",
|
||||||
"Enter the list of brokers": "Enter the list of brokers",
|
"Enter the list of brokers": "Enter the list of brokers",
|
||||||
"Press Enter to add broker": "Press Enter to add broker",
|
"Press Enter to add broker": "Press Enter to add broker",
|
||||||
|
|
|
@ -117,12 +117,23 @@
|
||||||
{{ $t("Settings") }}
|
{{ $t("Settings") }}
|
||||||
</router-link>
|
</router-link>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
<button
|
||||||
|
v-if="numActiveToasts != 0"
|
||||||
|
type="button"
|
||||||
|
class="btn btn-normal clear-all-toast-btn"
|
||||||
|
@click="clearToasts"
|
||||||
|
>
|
||||||
|
<font-awesome-icon icon="times" />
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Login from "../components/Login.vue";
|
import Login from "../components/Login.vue";
|
||||||
import compareVersions from "compare-versions";
|
import compareVersions from "compare-versions";
|
||||||
|
import { useToast } from "vue-toastification";
|
||||||
|
const toast = useToast();
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
||||||
|
@ -131,7 +142,11 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {};
|
return {
|
||||||
|
toastContainer: null,
|
||||||
|
numActiveToasts: 0,
|
||||||
|
toastContainerObserver: null,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -159,11 +174,33 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
|
this.toastContainer = document.querySelector(".bottom-right.toast-container");
|
||||||
|
|
||||||
|
// Watch the number of active toasts
|
||||||
|
this.toastContainerObserver = new MutationObserver((mutations) => {
|
||||||
|
for (const mutation of mutations) {
|
||||||
|
if (mutation.type === "childList") {
|
||||||
|
this.numActiveToasts = mutation.target.children.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this.toastContainer != null) {
|
||||||
|
this.toastContainerObserver.observe(this.toastContainer, { childList: true });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
beforeUnmount() {
|
||||||
|
this.toastContainerObserver.disconnect();
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
/**
|
||||||
|
* Clear all toast notifications.
|
||||||
|
*/
|
||||||
|
clearToasts() {
|
||||||
|
toast.clear();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@ -323,4 +360,22 @@ main {
|
||||||
background-color: $dark-bg;
|
background-color: $dark-bg;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.clear-all-toast-btn {
|
||||||
|
position: fixed;
|
||||||
|
right: 1em;
|
||||||
|
bottom: 1em;
|
||||||
|
font-size: 1.2em;
|
||||||
|
padding: 9px 15px;
|
||||||
|
width: 48px;
|
||||||
|
box-shadow: 2px 2px 30px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 770px) {
|
||||||
|
.clear-all-toast-btn {
|
||||||
|
bottom: 72px;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -20,6 +20,7 @@ import dayjs from "dayjs";
|
||||||
import timezone from "./modules/dayjs/plugin/timezone";
|
import timezone from "./modules/dayjs/plugin/timezone";
|
||||||
import utc from "dayjs/plugin/utc";
|
import utc from "dayjs/plugin/utc";
|
||||||
import relativeTime from "dayjs/plugin/relativeTime";
|
import relativeTime from "dayjs/plugin/relativeTime";
|
||||||
|
import { loadToastSettings } from "./util-frontend";
|
||||||
dayjs.extend(utc);
|
dayjs.extend(utc);
|
||||||
dayjs.extend(timezone);
|
dayjs.extend(timezone);
|
||||||
dayjs.extend(relativeTime);
|
dayjs.extend(relativeTime);
|
||||||
|
@ -44,11 +45,7 @@ const app = createApp({
|
||||||
app.use(router);
|
app.use(router);
|
||||||
app.use(i18n);
|
app.use(i18n);
|
||||||
|
|
||||||
const options = {
|
app.use(Toast, loadToastSettings());
|
||||||
position: "bottom-right",
|
|
||||||
};
|
|
||||||
|
|
||||||
app.use(Toast, options);
|
|
||||||
app.component("Editable", contenteditable);
|
app.component("Editable", contenteditable);
|
||||||
app.component("FontAwesomeIcon", FontAwesomeIcon);
|
app.component("FontAwesomeIcon", FontAwesomeIcon);
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ import jwtDecode from "jwt-decode";
|
||||||
import Favico from "favico.js";
|
import Favico from "favico.js";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { DOWN, MAINTENANCE, PENDING, UP } from "../util.ts";
|
import { DOWN, MAINTENANCE, PENDING, UP } from "../util.ts";
|
||||||
import { getDevContainerServerHostname, isDevContainer } from "../util-frontend.js";
|
import { getDevContainerServerHostname, isDevContainer, getToastSuccessTimeout, getToastErrorTimeout } from "../util-frontend.js";
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
|
|
||||||
let socket;
|
let socket;
|
||||||
|
@ -190,11 +190,11 @@ export default {
|
||||||
if (this.monitorList[data.monitorID] !== undefined) {
|
if (this.monitorList[data.monitorID] !== undefined) {
|
||||||
if (data.status === 0) {
|
if (data.status === 0) {
|
||||||
toast.error(`[${this.monitorList[data.monitorID].name}] [DOWN] ${data.msg}`, {
|
toast.error(`[${this.monitorList[data.monitorID].name}] [DOWN] ${data.msg}`, {
|
||||||
timeout: false,
|
timeout: getToastErrorTimeout(),
|
||||||
});
|
});
|
||||||
} else if (data.status === 1) {
|
} else if (data.status === 1) {
|
||||||
toast.success(`[${this.monitorList[data.monitorID].name}] [Up] ${data.msg}`, {
|
toast.success(`[${this.monitorList[data.monitorID].name}] [Up] ${data.msg}`, {
|
||||||
timeout: 20000,
|
timeout: getToastSuccessTimeout(),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
toast(`[${this.monitorList[data.monitorID].name}] ${data.msg}`);
|
toast(`[${this.monitorList[data.monitorID].name}] ${data.msg}`);
|
||||||
|
@ -683,7 +683,7 @@ export default {
|
||||||
*/
|
*/
|
||||||
getMonitorBeats(monitorID, period, callback) {
|
getMonitorBeats(monitorID, period, callback) {
|
||||||
socket.emit("getMonitorBeats", monitorID, period, callback);
|
socket.emit("getMonitorBeats", monitorID, period, callback);
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import timezones from "timezones-list";
|
import timezones from "timezones-list";
|
||||||
import { localeDirection, currentLocale } from "./i18n";
|
import { localeDirection, currentLocale } from "./i18n";
|
||||||
|
import { POSITION } from "vue-toastification";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the offset from UTC in hours for the current locale.
|
* Returns the offset from UTC in hours for the current locale.
|
||||||
|
@ -149,3 +150,65 @@ export function colorOptions(self) {
|
||||||
color: "#DB2777" },
|
color: "#DB2777" },
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the toast timeout settings from storage.
|
||||||
|
* @returns {object} The toast plugin options object.
|
||||||
|
*/
|
||||||
|
export function loadToastSettings() {
|
||||||
|
return {
|
||||||
|
position: POSITION.BOTTOM_RIGHT,
|
||||||
|
containerClassName: "toast-container mb-5",
|
||||||
|
showCloseButtonOnHover: true,
|
||||||
|
|
||||||
|
filterBeforeCreate: (toast, toasts) => {
|
||||||
|
if (toast.timeout === 0) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return toast;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get timeout for success toasts
|
||||||
|
* @returns {(number|boolean)} Timeout in ms. If false timeout disabled.
|
||||||
|
*/
|
||||||
|
export function getToastSuccessTimeout() {
|
||||||
|
let successTimeout = 20000;
|
||||||
|
|
||||||
|
if (localStorage.toastSuccessTimeout !== undefined) {
|
||||||
|
const parsedTimeout = parseInt(localStorage.toastSuccessTimeout);
|
||||||
|
if (parsedTimeout != null && !Number.isNaN(parsedTimeout)) {
|
||||||
|
successTimeout = parsedTimeout;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (successTimeout === -1) {
|
||||||
|
successTimeout = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return successTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get timeout for error toasts
|
||||||
|
* @returns {(number|boolean)} Timeout in ms. If false timeout disabled.
|
||||||
|
*/
|
||||||
|
export function getToastErrorTimeout() {
|
||||||
|
let errorTimeout = -1;
|
||||||
|
|
||||||
|
if (localStorage.toastErrorTimeout !== undefined) {
|
||||||
|
const parsedTimeout = parseInt(localStorage.toastErrorTimeout);
|
||||||
|
if (parsedTimeout != null && !Number.isNaN(parsedTimeout)) {
|
||||||
|
errorTimeout = parsedTimeout;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errorTimeout === -1) {
|
||||||
|
errorTimeout = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return errorTimeout;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue