summaryrefslogtreecommitdiffstats
path: root/src/pages/EditMaintenance.vue
diff options
context:
space:
mode:
Diffstat (limited to 'src/pages/EditMaintenance.vue')
-rw-r--r--src/pages/EditMaintenance.vue611
1 files changed, 611 insertions, 0 deletions
diff --git a/src/pages/EditMaintenance.vue b/src/pages/EditMaintenance.vue
new file mode 100644
index 0000000..953fe33
--- /dev/null
+++ b/src/pages/EditMaintenance.vue
@@ -0,0 +1,611 @@
+<template>
+ <transition name="slide-fade" appear>
+ <div>
+ <h1 class="mb-3">{{ pageName }}</h1>
+ <form @submit.prevent="submit">
+ <div class="shadow-box shadow-box-with-fixed-bottom-bar">
+ <div class="row">
+ <div class="col-xl-10">
+ <!-- Title -->
+ <div class="mb-3">
+ <label for="name" class="form-label">{{ $t("Title") }}</label>
+ <input
+ id="name" v-model="maintenance.title" type="text" class="form-control"
+ required
+ >
+ </div>
+
+ <!-- Description -->
+ <div class="my-3">
+ <label for="description" class="form-label">{{ $t("Description") }}</label>
+ <textarea
+ id="description" v-model="maintenance.description" class="form-control"
+ ></textarea>
+ <div class="form-text">
+ {{ $t("markdownSupported") }}
+ </div>
+ </div>
+
+ <!-- Affected Monitors -->
+ <h2 class="mt-5">{{ $t("Affected Monitors") }}</h2>
+ {{ $t("affectedMonitorsDescription") }}
+
+ <div class="my-3">
+ <VueMultiselect
+ id="affected_monitors"
+ v-model="affectedMonitors"
+ :options="affectedMonitorsOptions"
+ track-by="id"
+ label="pathName"
+ :multiple="true"
+ :close-on-select="false"
+ :clear-on-select="false"
+ :preserve-search="true"
+ :placeholder="$t('Pick Affected Monitors...')"
+ :preselect-first="false"
+ :max-height="600"
+ :taggable="false"
+ ></VueMultiselect>
+ </div>
+
+ <!-- Status pages to display maintenance info on -->
+ <h2 class="mt-5">{{ $t("Status Pages") }}</h2>
+ {{ $t("affectedStatusPages") }}
+
+ <div class="my-3">
+ <!-- Show on all pages -->
+ <div class="form-check mb-2">
+ <input
+ id="show-on-all-pages" v-model="showOnAllPages" class="form-check-input"
+ type="checkbox"
+ >
+ <label class="form-check-label" for="show-powered-by">{{
+ $t("All Status Pages")
+ }}</label>
+ </div>
+
+ <div v-if="!showOnAllPages">
+ <VueMultiselect
+ id="selected_status_pages"
+ v-model="selectedStatusPages"
+ :options="selectedStatusPagesOptions"
+ track-by="id"
+ label="name"
+ :multiple="true"
+ :close-on-select="false"
+ :clear-on-select="false"
+ :preserve-search="true"
+ :placeholder="$t('Select status pages...')"
+ :preselect-first="false"
+ :max-height="600"
+ :taggable="false"
+ ></VueMultiselect>
+ </div>
+ </div>
+
+ <h2 class="mt-5">{{ $t("Date and Time") }}</h2>
+
+ <!-- Strategy -->
+ <div class="my-3">
+ <label for="strategy" class="form-label">{{ $t("Strategy") }}</label>
+ <select id="strategy" v-model="maintenance.strategy" class="form-select">
+ <option value="manual">{{ $t("strategyManual") }}</option>
+ <option value="single">{{ $t("Single Maintenance Window") }}</option>
+ <option value="cron">{{ $t("cronExpression") }}</option>
+ <option value="recurring-interval">{{ $t("Recurring") }} - {{ $t("recurringInterval") }}</option>
+ <option value="recurring-weekday">{{ $t("Recurring") }} - {{ $t("dayOfWeek") }}</option>
+ <option value="recurring-day-of-month">{{ $t("Recurring") }} - {{ $t("dayOfMonth") }}</option>
+ </select>
+ </div>
+
+ <!-- Single Maintenance Window -->
+ <template v-if="maintenance.strategy === 'single'">
+ </template>
+
+ <template v-if="maintenance.strategy === 'cron'">
+ <!-- Cron -->
+ <div class="my-3">
+ <label for="cron" class="form-label">
+ {{ $t("cronExpression") }}
+ </label>
+ <p>{{ $t("cronSchedule") }}{{ cronDescription }}</p>
+ <input id="cron" v-model="maintenance.cron" type="text" class="form-control" required>
+ </div>
+
+ <div class="my-3">
+ <!-- Duration -->
+ <label for="duration" class="form-label">
+ {{ $t("Duration (Minutes)") }}
+ </label>
+ <input id="duration" v-model="maintenance.durationMinutes" type="number" class="form-control" required min="1" step="1">
+ </div>
+ </template>
+
+ <!-- Recurring - Interval -->
+ <template v-if="maintenance.strategy === 'recurring-interval'">
+ <div class="my-3">
+ <label for="interval-day" class="form-label">
+ {{ $t("recurringInterval") }}
+
+ <template v-if="maintenance.intervalDay >= 1">
+ ({{
+ $tc("recurringIntervalMessage", maintenance.intervalDay, [
+ maintenance.intervalDay
+ ])
+ }})
+ </template>
+ </label>
+ <input id="interval-day" v-model="maintenance.intervalDay" type="number" class="form-control" required min="1" max="3650" step="1">
+ </div>
+ </template>
+
+ <!-- Recurring - Weekday -->
+ <template v-if="maintenance.strategy === 'recurring-weekday'">
+ <div class="my-3">
+ <label for="interval-day" class="form-label">
+ {{ $t("dayOfWeek") }}
+ </label>
+
+ <!-- Weekday Picker -->
+ <div class="weekday-picker">
+ <div v-for="(weekday, index) in weekdays" :key="index">
+ <label class="form-check-label" :for="weekday.id">{{ $t(weekday.langKey) }}</label>
+ <div class="form-check-inline"><input :id="weekday.id" v-model="maintenance.weekdays" type="checkbox" :value="weekday.value" class="form-check-input"></div>
+ </div>
+ </div>
+ </div>
+ </template>
+
+ <!-- Recurring - Day of month -->
+ <template v-if="maintenance.strategy === 'recurring-day-of-month'">
+ <div class="my-3">
+ <label for="interval-day" class="form-label">
+ {{ $t("dayOfMonth") }}
+ </label>
+
+ <!-- Day Picker -->
+ <div class="day-picker">
+ <div v-for="index in 31" :key="index">
+ <label class="form-check-label" :for="'day' + index">{{ index }}</label>
+ <div class="form-check-inline">
+ <input :id="'day' + index" v-model="maintenance.daysOfMonth" type="checkbox" :value="index" class="form-check-input">
+ </div>
+ </div>
+ </div>
+
+ <div class="mt-3 mb-2">{{ $t("lastDay") }}</div>
+
+ <div v-for="(lastDay, index) in lastDays" :key="index" class="form-check">
+ <input :id="lastDay.langKey" v-model="maintenance.daysOfMonth" type="checkbox" :value="lastDay.value" class="form-check-input">
+ <label class="form-check-label" :for="lastDay.langKey">
+ {{ $t(lastDay.langKey) }}
+ </label>
+ </div>
+ </div>
+ </template>
+
+ <template v-if="maintenance.strategy === 'recurring-interval' || maintenance.strategy === 'recurring-weekday' || maintenance.strategy === 'recurring-day-of-month'">
+ <!-- Maintenance Time Window of a Day -->
+ <div class="my-3">
+ <label class="form-label">{{ $t("Maintenance Time Window of a Day") }}</label>
+ <Datepicker
+ v-model="maintenance.timeRange"
+ :dark="$root.isDark"
+ timePicker
+ disableTimeRangeValidation range
+ />
+ </div>
+ </template>
+
+ <template v-if="maintenance.strategy === 'recurring-interval' || maintenance.strategy === 'recurring-weekday' || maintenance.strategy === 'recurring-day-of-month' || maintenance.strategy === 'cron' || maintenance.strategy === 'single'">
+ <!-- Timezone -->
+ <div class="mb-4">
+ <label for="timezone" class="form-label">
+ {{ $t("Timezone") }}
+ </label>
+ <select id="timezone" v-model="maintenance.timezoneOption" class="form-select">
+ <option value="SAME_AS_SERVER">{{ $t("sameAsServerTimezone") }}</option>
+ <option value="UTC">UTC</option>
+ <option
+ v-for="(timezone, index) in timezoneList"
+ :key="index"
+ :value="timezone.value"
+ >
+ {{ timezone.name }}
+ </option>
+ </select>
+ </div>
+
+ <!-- Date Range -->
+ <div class="my-3">
+ <label v-if="maintenance.strategy !== 'single'" class="form-label">{{ $t("Effective Date Range") }}</label>
+
+ <div class="row">
+ <div class="col">
+ <div class="mb-2">{{ $t("startDateTime") }}</div>
+ <input v-model="maintenance.dateRange[0]" type="datetime-local" class="form-control" :required="maintenance.strategy === 'single'">
+ </div>
+
+ <div class="col">
+ <div class="mb-2">{{ $t("endDateTime") }}</div>
+ <input v-model="maintenance.dateRange[1]" type="datetime-local" class="form-control" :required="maintenance.strategy === 'single'">
+ </div>
+ </div>
+ </div>
+ </template>
+ </div>
+ </div>
+
+ <div class="fixed-bottom-bar p-3">
+ <button id="monitor-submit-btn" class="btn btn-primary" type="submit" :disabled="processing">{{ $t("Save") }}</button>
+ </div>
+ </div>
+ </form>
+ </div>
+ </transition>
+</template>
+
+<script>
+import VueMultiselect from "vue-multiselect";
+import Datepicker from "@vuepic/vue-datepicker";
+import { timezoneList } from "../util-frontend";
+import cronstrue from "cronstrue/i18n";
+
+export default {
+ components: {
+ VueMultiselect,
+ Datepicker
+ },
+
+ data() {
+ return {
+ timezoneList: timezoneList(),
+ processing: false,
+ maintenance: {},
+ affectedMonitors: [],
+ affectedMonitorsOptions: [],
+ showOnAllPages: false,
+ selectedStatusPages: [],
+ dark: (this.$root.theme === "dark"),
+ neverEnd: false,
+ lastDays: [
+ {
+ langKey: "lastDay1",
+ value: "lastDay1",
+ },
+ ],
+ weekdays: [
+ {
+ id: "weekday1",
+ langKey: "weekdayShortMon",
+ value: 1,
+ },
+ {
+ id: "weekday2",
+ langKey: "weekdayShortTue",
+ value: 2,
+ },
+ {
+ id: "weekday3",
+ langKey: "weekdayShortWed",
+ value: 3,
+ },
+ {
+ id: "weekday4",
+ langKey: "weekdayShortThu",
+ value: 4,
+ },
+ {
+ id: "weekday5",
+ langKey: "weekdayShortFri",
+ value: 5,
+ },
+ {
+ id: "weekday6",
+ langKey: "weekdayShortSat",
+ value: 6,
+ },
+ {
+ id: "weekday0",
+ langKey: "weekdayShortSun",
+ value: 0,
+ },
+ ],
+ };
+ },
+
+ computed: {
+
+ cronDescription() {
+ if (! this.maintenance.cron) {
+ return "";
+ }
+
+ let locale = "";
+
+ if (this.$root.language) {
+ locale = this.$root.language.replace("-", "_");
+ }
+
+ // Special handling
+ // If locale is also not working in your language, you can map it here
+ // https://github.com/bradymholt/cRonstrue/tree/master/src/i18n/locales
+ if (locale === "zh_HK") {
+ locale = "zh_TW";
+ }
+
+ try {
+ return cronstrue.toString(this.maintenance.cron, {
+ locale,
+ });
+ } catch (e) {
+ return this.$t("invalidCronExpression", e.message);
+ }
+
+ },
+
+ selectedStatusPagesOptions() {
+ return Object.values(this.$root.statusPageList).map(statusPage => {
+ return {
+ id: statusPage.id,
+ name: statusPage.title
+ };
+ });
+ },
+
+ pageName() {
+ return this.$t((this.isAdd) ? "Schedule Maintenance" : "Edit Maintenance");
+ },
+
+ isAdd() {
+ return this.$route.path === "/add-maintenance";
+ },
+
+ isEdit() {
+ return this.$route.path.startsWith("/maintenance/edit");
+ },
+
+ },
+ watch: {
+ "$route.fullPath"() {
+ this.init();
+ },
+
+ neverEnd(value) {
+ if (value) {
+ this.maintenance.recurringEndDate = "";
+ }
+ },
+ },
+ mounted() {
+ this.$root.getMonitorList((res) => {
+ if (res.ok) {
+ 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,
+ pathName: monitor.pathName,
+ });
+ });
+ }
+ this.init();
+ });
+ },
+ methods: {
+ /**
+ * Initialise page
+ * @returns {void}
+ */
+ init() {
+ this.affectedMonitors = [];
+ this.selectedStatusPages = [];
+
+ if (this.isAdd) {
+ this.maintenance = {
+ title: "",
+ description: "",
+ strategy: "single",
+ active: 1,
+ cron: "30 3 * * *",
+ durationMinutes: 60,
+ intervalDay: 1,
+ dateRange: [],
+ timeRange: [{
+ hours: 2,
+ minutes: 0,
+ }, {
+ hours: 3,
+ minutes: 0,
+ }],
+ weekdays: [],
+ daysOfMonth: [],
+ timezoneOption: null,
+ };
+ } else if (this.isEdit) {
+ this.$root.getSocket().emit("getMaintenance", this.$route.params.id, (res) => {
+ if (res.ok) {
+ this.maintenance = res.maintenance;
+
+ this.$root.getSocket().emit("getMonitorMaintenance", this.$route.params.id, (res) => {
+ if (res.ok) {
+ Object.values(res.monitors).map(monitor => {
+ this.affectedMonitors.push(this.affectedMonitorsOptions.find(item => item.id === monitor.id));
+ });
+ } else {
+ this.$root.toastError(res.msg);
+ }
+ });
+
+ this.$root.getSocket().emit("getMaintenanceStatusPage", this.$route.params.id, (res) => {
+ if (res.ok) {
+ Object.values(res.statusPages).map(statusPage => {
+ this.selectedStatusPages.push({
+ id: statusPage.id,
+ name: statusPage.title
+ });
+ });
+
+ this.showOnAllPages = Object.values(res.statusPages).length === this.selectedStatusPagesOptions.length;
+ } else {
+ this.$root.toastError(res.msg);
+ }
+ });
+ } else {
+ this.$root.toastError(res.msg);
+ }
+ });
+ }
+ },
+
+ /**
+ * Create new maintenance
+ * @returns {Promise<void>}
+ */
+ async submit() {
+ this.processing = true;
+
+ if (this.affectedMonitors.length === 0) {
+ this.$root.toastError(this.$t("atLeastOneMonitor"));
+ return this.processing = false;
+ }
+
+ if (this.isAdd) {
+ this.$root.addMaintenance(this.maintenance, async (res) => {
+ if (res.ok) {
+ await this.addMonitorMaintenance(res.maintenanceID, async () => {
+ await this.addMaintenanceStatusPage(res.maintenanceID, () => {
+ this.$root.toastRes(res);
+ this.processing = false;
+ this.$root.getMaintenanceList();
+ this.$router.push("/maintenance");
+ });
+ });
+ } else {
+ this.$root.toastRes(res);
+ this.processing = false;
+ }
+
+ });
+ } else {
+ this.$root.getSocket().emit("editMaintenance", this.maintenance, async (res) => {
+ if (res.ok) {
+ await this.addMonitorMaintenance(res.maintenanceID, async () => {
+ await this.addMaintenanceStatusPage(res.maintenanceID, () => {
+ this.processing = false;
+ this.$root.toastRes(res);
+ this.init();
+ this.$router.push("/maintenance");
+ });
+ });
+ } else {
+ this.processing = false;
+ this.$root.toastError(res.msg);
+ }
+ });
+ }
+ },
+
+ /**
+ * Add monitor to maintenance
+ * @param {number} maintenanceID ID of maintenance to modify
+ * @param {socketCB} callback Callback for socket response
+ * @returns {Promise<void>}
+ */
+ async addMonitorMaintenance(maintenanceID, callback) {
+ await this.$root.addMonitorMaintenance(maintenanceID, this.affectedMonitors, async (res) => {
+ if (!res.ok) {
+ this.$root.toastError(res.msg);
+ } else {
+ this.$root.getMonitorList();
+ }
+
+ callback();
+ });
+ },
+
+ /**
+ * Add status page to maintenance
+ * @param {number} maintenanceID ID of maintenance to modify
+ * @param {socketCB} callback Callback for socket response
+ * @returns {void}
+ */
+ async addMaintenanceStatusPage(maintenanceID, callback) {
+ await this.$root.addMaintenanceStatusPage(maintenanceID, (this.showOnAllPages) ? this.selectedStatusPagesOptions : this.selectedStatusPages, async (res) => {
+ if (!res.ok) {
+ this.$root.toastError(res.msg);
+ } else {
+ this.$root.getMaintenanceList();
+ }
+
+ callback();
+ });
+ },
+ },
+};
+</script>
+
+<style lang="scss" scoped>
+textarea {
+ min-height: 150px;
+}
+
+.dark-calendar::-webkit-calendar-picker-indicator {
+ filter: invert(1);
+}
+
+.weekday-picker {
+ display: flex;
+ gap: 10px;
+
+ & > div {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ width: 40px;
+
+ .form-check-inline {
+ margin-right: 0;
+ }
+ }
+}
+
+.day-picker {
+ display: flex;
+ gap: 10px;
+ flex-wrap: wrap;
+
+ & > div {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ width: 40px;
+
+ .form-check-inline {
+ margin-right: 0;
+ }
+ }
+}
+
+</style>