diff --git a/src/components/CreateGroupDialog.vue b/src/components/CreateGroupDialog.vue index ba7fe6eb7..8bac1ccd0 100644 --- a/src/components/CreateGroupDialog.vue +++ b/src/components/CreateGroupDialog.vue @@ -42,6 +42,9 @@ export default { mounted() { this.modal = new Modal(this.$refs.modal); }, + beforeUnmount() { + this.cleanupModal(); + }, methods: { /** * Show the confirm dialog @@ -58,6 +61,19 @@ export default { this.$emit("added", this.groupName); this.modal.hide(); }, + /** + * Clean up modal and restore scroll behavior + * @returns {void} + */ + cleanupModal() { + if (this.modal) { + try { + this.modal.hide(); + } catch (e) { + console.warn("Modal hide failed:", e); + } + } + } }, }; diff --git a/src/components/NextPingTimer.vue b/src/components/NextPingTimer.vue new file mode 100644 index 000000000..8bb30f0a4 --- /dev/null +++ b/src/components/NextPingTimer.vue @@ -0,0 +1,54 @@ + + + + + diff --git a/src/components/NotificationDialog.vue b/src/components/NotificationDialog.vue index f6d728029..8a747072f 100644 --- a/src/components/NotificationDialog.vue +++ b/src/components/NotificationDialog.vue @@ -232,6 +232,9 @@ export default { mounted() { this.modal = new Modal(this.$refs.modal); }, + beforeUnmount() { + this.cleanupModal(); + }, methods: { /** @@ -336,6 +339,20 @@ export default { }); } while (this.$root.notificationList.find(it => it.name === name)); return name; + }, + + /** + * Clean up modal and restore scroll behavior + * @returns {void} + */ + cleanupModal() { + if (this.modal) { + try { + this.modal.hide(); + } catch (e) { + console.warn("Modal hide failed:", e); + } + } } }, }; diff --git a/src/components/ProxyDialog.vue b/src/components/ProxyDialog.vue index fc92359b9..2f7ed7b61 100644 --- a/src/components/ProxyDialog.vue +++ b/src/components/ProxyDialog.vue @@ -125,11 +125,12 @@ export default { } }; }, - mounted() { this.modal = new Modal(this.$refs.modal); }, - + beforeUnmount() { + this.cleanupModal(); + }, methods: { /** * Show dialog to confirm deletion @@ -209,6 +210,20 @@ export default { } }); }, + + /** + * Clean up modal and restore scroll behavior + * @returns {void} + */ + cleanupModal() { + if (this.modal) { + try { + this.modal.hide(); + } catch (e) { + console.warn("Modal hide failed:", e); + } + } + } }, }; diff --git a/src/components/TagsManager.vue b/src/components/TagsManager.vue index 19c8e481b..368ea18c2 100644 --- a/src/components/TagsManager.vue +++ b/src/components/TagsManager.vue @@ -248,6 +248,9 @@ export default { this.modal = new Modal(this.$refs.modal); this.getExistingTags(); }, + beforeUnmount() { + this.cleanupModal(); + }, methods: { /** * Show the add tag dialog @@ -459,6 +462,19 @@ export default { this.newTags = []; this.deleteTags = []; this.processing = false; + }, + /** + * Clean up modal and restore scroll behavior + * @returns {void} + */ + cleanupModal() { + if (this.modal) { + try { + this.modal.hide(); + } catch (e) { + console.warn("Modal hide failed:", e); + } + } } }, }; diff --git a/src/lang/en.json b/src/lang/en.json index d56e61bdf..0187c0219 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -1059,6 +1059,7 @@ "RabbitMQ Username": "RabbitMQ Username", "RabbitMQ Password": "RabbitMQ Password", "rabbitmqHelpText": "To use the monitor, you will need to enable the Management Plugin in your RabbitMQ setup. For more information, please consult the {rabitmq_documentation}.", + "nextCheckIn": "Next in ", "SendGrid API Key": "SendGrid API Key", "Separate multiple email addresses with commas": "Separate multiple email addresses with commas" } diff --git a/src/pages/Details.vue b/src/pages/Details.vue index 17d32365c..c712fb066 100644 --- a/src/pages/Details.vue +++ b/src/pages/Details.vue @@ -76,7 +76,10 @@
- {{ $t("checkEverySecond", [ monitor.interval ]) }} +
+ {{ $t("checkEverySecond", [ monitor.interval ]) }} + +
{{ status.text }} @@ -275,6 +278,7 @@ import { useToast } from "vue-toastification"; const toast = useToast(); import Confirm from "../components/Confirm.vue"; import HeartbeatBar from "../components/HeartbeatBar.vue"; +import NextPingTimer from "../components/NextPingTimer.vue"; import Status from "../components/Status.vue"; import Datetime from "../components/Datetime.vue"; import CountUp from "../components/CountUp.vue"; @@ -293,6 +297,7 @@ import "prismjs/components/prism-css"; import { PrismEditor } from "vue-prism-editor"; import "vue-prism-editor/dist/prismeditor.min.css"; import ScreenshotDialog from "../components/ScreenshotDialog.vue"; +import dayjs from "dayjs"; export default { components: { @@ -300,6 +305,7 @@ export default { CountUp, Datetime, HeartbeatBar, + NextPingTimer, Confirm, Status, Pagination, @@ -313,6 +319,7 @@ export default { return { page: 1, perPage: 25, + timeRemaining: 0, heartBeatList: [], toggleCertInfoBox: false, showPingChartBox: true, @@ -340,6 +347,8 @@ export default { // Also trigger screenshot refresh here // eslint-disable-next-line vue/no-side-effects-in-computed-properties this.cacheTime = Date.now(); + // eslint-disable-next-line vue/no-side-effects-in-computed-properties + this.timeRemaining = this.monitor.interval; if (this.monitor.id in this.$root.lastHeartbeatList && this.$root.lastHeartbeatList[this.monitor.id]) { return this.$root.lastHeartbeatList[this.monitor.id]; @@ -399,7 +408,7 @@ export default { screenshotURL() { return getResBaseURL() + this.monitor.screenshot + "?time=" + this.cacheTime; - } + }, }, watch: { @@ -419,7 +428,14 @@ export default { this.loadPushExample(); }, }, - + created() { + if (this.lastHeartBeat.end_time) { + const lastpingtime = dayjs().utc().diff(dayjs.utc(this.lastHeartBeat.end_time), "seconds"); + this.timeRemaining = this.monitor.interval - lastpingtime; + } else { + this.timeRemaining = this.monitor.interval; + } + }, mounted() { this.getImportantHeartbeatListLength(); @@ -431,10 +447,17 @@ export default { } this.loadPushExample(); } + + this.interval = setInterval(() => { + if (this.timeRemaining > 1) { + this.timeRemaining--; + } + }, 1000); }, beforeUnmount() { this.$root.emitter.off("newImportantHeartbeat", this.onNewImportantHeartbeat); + clearInterval(this.interval); }, methods: { @@ -464,6 +487,7 @@ export default { this.$root.getSocket().emit("resumeMonitor", this.monitor.id, (res) => { this.$root.toastRes(res); }); + this.timeRemaining = this.monitor.interval; }, /**