mirror of
https://github.com/louislam/uptime-kuma.git
synced 2025-08-21 07:27:01 +08:00
Merge branch 'master' into 1.23.X-merge-to-2.X.X
# Conflicts: # docker/debian-base.dockerfile # package-lock.json # server/database.js # server/model/monitor.js # server/uptime-kuma-server.js # server/util-server.js
This commit is contained in:
@@ -51,7 +51,10 @@ export default {
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
/** Submit form data to add new status page */
|
||||
/**
|
||||
* Submit form data to add new status page
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async submit() {
|
||||
this.processing = true;
|
||||
|
||||
@@ -63,7 +66,7 @@ export default {
|
||||
} else {
|
||||
|
||||
if (res.msg.includes("UNIQUE constraint")) {
|
||||
this.$root.toastError(this.$t("The slug is already taken. Please choose another slug."));
|
||||
this.$root.toastError("The slug is already taken. Please choose another slug.");
|
||||
} else {
|
||||
this.$root.toastRes(res);
|
||||
}
|
||||
|
@@ -42,13 +42,13 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(beat, index) in displayedRecords" :key="index" :class="{ 'shadow-box': $root.windowWidth <= 550}">
|
||||
<td><router-link :to="`/dashboard/${beat.monitorID}`">{{ beat.name }}</router-link></td>
|
||||
<td><router-link :to="`/dashboard/${beat.monitorID}`">{{ $root.monitorList[beat.monitorID]?.name }}</router-link></td>
|
||||
<td><Status :status="beat.status" /></td>
|
||||
<td :class="{ 'border-0':! beat.msg}"><Datetime :value="beat.time" /></td>
|
||||
<td class="border-0">{{ beat.msg }}</td>
|
||||
</tr>
|
||||
|
||||
<tr v-if="importantHeartBeatList.length === 0">
|
||||
<tr v-if="importantHeartBeatListLength === 0">
|
||||
<td colspan="4">
|
||||
{{ $t("No important events") }}
|
||||
</td>
|
||||
@@ -59,7 +59,7 @@
|
||||
<div class="d-flex justify-content-center kuma_pagination">
|
||||
<pagination
|
||||
v-model="page"
|
||||
:records="importantHeartBeatList.length"
|
||||
:records="importantHeartBeatListLength"
|
||||
:per-page="perPage"
|
||||
:options="paginationConfig"
|
||||
/>
|
||||
@@ -92,72 +92,89 @@ export default {
|
||||
page: 1,
|
||||
perPage: 25,
|
||||
initialPerPage: 25,
|
||||
heartBeatList: [],
|
||||
paginationConfig: {
|
||||
hideCount: true,
|
||||
chunksNavigation: "scroll",
|
||||
},
|
||||
importantHeartBeatListLength: 0,
|
||||
displayedRecords: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
||||
importantHeartBeatList() {
|
||||
let result = [];
|
||||
|
||||
for (let monitorID in this.$root.importantHeartbeatList) {
|
||||
let list = this.$root.importantHeartbeatList[monitorID];
|
||||
result = result.concat(list);
|
||||
}
|
||||
|
||||
for (let beat of result) {
|
||||
let monitor = this.$root.monitorList[beat.monitorID];
|
||||
|
||||
if (monitor) {
|
||||
beat.name = monitor.name;
|
||||
}
|
||||
}
|
||||
|
||||
result.sort((a, b) => {
|
||||
if (a.time > b.time) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (a.time < b.time) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
});
|
||||
|
||||
// eslint-disable-next-line vue/no-side-effects-in-computed-properties
|
||||
this.heartBeatList = result;
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
displayedRecords() {
|
||||
const startIndex = this.perPage * (this.page - 1);
|
||||
const endIndex = startIndex + this.perPage;
|
||||
return this.heartBeatList.slice(startIndex, endIndex);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
importantHeartBeatList() {
|
||||
perPage() {
|
||||
this.$nextTick(() => {
|
||||
this.updatePerPage();
|
||||
this.getImportantHeartbeatListPaged();
|
||||
});
|
||||
},
|
||||
|
||||
page() {
|
||||
this.getImportantHeartbeatListPaged();
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.getImportantHeartbeatListLength();
|
||||
|
||||
this.$root.emitter.on("newImportantHeartbeat", this.onNewImportantHeartbeat);
|
||||
|
||||
this.initialPerPage = this.perPage;
|
||||
|
||||
window.addEventListener("resize", this.updatePerPage);
|
||||
this.updatePerPage();
|
||||
},
|
||||
|
||||
beforeUnmount() {
|
||||
this.$root.emitter.off("newImportantHeartbeat", this.onNewImportantHeartbeat);
|
||||
|
||||
window.removeEventListener("resize", this.updatePerPage);
|
||||
},
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* Updates the displayed records when a new important heartbeat arrives.
|
||||
* @param {object} heartbeat - The heartbeat object received.
|
||||
* @returns {void}
|
||||
*/
|
||||
onNewImportantHeartbeat(heartbeat) {
|
||||
if (this.page === 1) {
|
||||
this.displayedRecords.unshift(heartbeat);
|
||||
if (this.displayedRecords.length > this.perPage) {
|
||||
this.displayedRecords.pop();
|
||||
}
|
||||
this.importantHeartBeatListLength += 1;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieves the length of the important heartbeat list for all monitors.
|
||||
* @returns {void}
|
||||
*/
|
||||
getImportantHeartbeatListLength() {
|
||||
this.$root.getSocket().emit("monitorImportantHeartbeatListCount", null, (res) => {
|
||||
if (res.ok) {
|
||||
this.importantHeartBeatListLength = res.count;
|
||||
this.getImportantHeartbeatListPaged();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieves the important heartbeat list for the current page.
|
||||
* @returns {void}
|
||||
*/
|
||||
getImportantHeartbeatListPaged() {
|
||||
const offset = (this.page - 1) * this.perPage;
|
||||
this.$root.getSocket().emit("monitorImportantHeartbeatListPaged", null, offset, this.perPage, (res) => {
|
||||
if (res.ok) {
|
||||
this.displayedRecords = res.data;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the number of items shown per page based on the available height.
|
||||
* @returns {void}
|
||||
*/
|
||||
updatePerPage() {
|
||||
const tableContainer = this.$refs.tableContainer;
|
||||
const tableContainerHeight = tableContainer.offsetHeight;
|
||||
|
@@ -76,6 +76,34 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Push Examples -->
|
||||
<div v-if="monitor.type === 'push'" class="shadow-box big-padding">
|
||||
<a href="#" @click="pushMonitor.showPushExamples = !pushMonitor.showPushExamples">{{ $t("pushViewCode") }}</a>
|
||||
|
||||
<transition name="slide-fade" appear>
|
||||
<div v-if="pushMonitor.showPushExamples" class="mt-3">
|
||||
<select id="push-current-example" v-model="pushMonitor.currentExample" class="form-select">
|
||||
<optgroup :label="$t('programmingLanguages')">
|
||||
<option value="csharp">C#</option>
|
||||
<option value="go">Go</option>
|
||||
<option value="java">Java</option>
|
||||
<option value="javascript-fetch">JavaScript (fetch)</option>
|
||||
<option value="php">PHP</option>
|
||||
<option value="python">Python</option>
|
||||
<option value="typescript-fetch">TypeScript (fetch)</option>
|
||||
</optgroup>
|
||||
<optgroup :label="$t('pushOthers')">
|
||||
<option value="bash-curl">Bash (curl)</option>
|
||||
<option value="powershell">PowerShell</option>
|
||||
<option value="docker">Docker</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
|
||||
<prism-editor v-model="pushMonitor.code" class="css-editor mt-3" :highlight="pushExampleHighlighter" line-numbers readonly></prism-editor>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
|
||||
<!-- Stats -->
|
||||
<div class="shadow-box big-padding text-center stats">
|
||||
<div class="row">
|
||||
@@ -95,6 +123,8 @@
|
||||
<CountUp :value="avgPing" />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Uptime (24-hour) -->
|
||||
<div class="col-12 col-sm col row d-flex align-items-center d-sm-block">
|
||||
<h4 class="col-4 col-sm-12">{{ $t("Uptime") }}</h4>
|
||||
<p class="col-4 col-sm-12 mb-0 mb-sm-2">(24{{ $t("-hour") }})</p>
|
||||
@@ -102,6 +132,8 @@
|
||||
<Uptime :monitor="monitor" type="24" />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Uptime (30-day) -->
|
||||
<div class="col-12 col-sm col row d-flex align-items-center d-sm-block">
|
||||
<h4 class="col-4 col-sm-12">{{ $t("Uptime") }}</h4>
|
||||
<p class="col-4 col-sm-12 mb-0 mb-sm-2">(30{{ $t("-day") }})</p>
|
||||
@@ -110,6 +142,15 @@
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Uptime (1-year) -->
|
||||
<div class="col-12 col-sm col row d-flex align-items-center d-sm-block">
|
||||
<h4 class="col-4 col-sm-12">{{ $t("Uptime") }}</h4>
|
||||
<p class="col-4 col-sm-12 mb-0 mb-sm-2">(1{{ $t("-year") }})</p>
|
||||
<span class="col-4 col-sm-12 num">
|
||||
<Uptime :monitor="monitor" type="1y" />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div v-if="tlsInfo" class="col-12 col-sm col row d-flex align-items-center d-sm-block">
|
||||
<h4 class="col-4 col-sm-12">{{ $t("Cert Exp.") }}</h4>
|
||||
<p class="col-4 col-sm-12 mb-0 mb-sm-2">(<Datetime :value="tlsInfo.certInfo.validTo" date-only />)</p>
|
||||
@@ -182,7 +223,7 @@
|
||||
<td class="border-0">{{ beat.msg }}</td>
|
||||
</tr>
|
||||
|
||||
<tr v-if="importantHeartBeatList.length === 0">
|
||||
<tr v-if="importantHeartBeatListLength === 0">
|
||||
<td colspan="3">
|
||||
{{ $t("No important events") }}
|
||||
</td>
|
||||
@@ -193,7 +234,7 @@
|
||||
<div class="d-flex justify-content-center kuma_pagination">
|
||||
<pagination
|
||||
v-model="page"
|
||||
:records="importantHeartBeatList.length"
|
||||
:records="importantHeartBeatListLength"
|
||||
:per-page="perPage"
|
||||
:options="paginationConfig"
|
||||
/>
|
||||
@@ -236,6 +277,12 @@ import CertificateInfo from "../components/CertificateInfo.vue";
|
||||
import { getMonitorRelativeURL } from "../util.ts";
|
||||
import { URL } from "whatwg-url";
|
||||
import { getResBaseURL } from "../util-frontend";
|
||||
import { highlight, languages } from "prismjs/components/prism-core";
|
||||
import "prismjs/components/prism-clike";
|
||||
import "prismjs/components/prism-javascript";
|
||||
import "prismjs/components/prism-css";
|
||||
import { PrismEditor } from "vue-prism-editor";
|
||||
import "vue-prism-editor/dist/prismeditor.min.css";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -249,6 +296,7 @@ export default {
|
||||
PingChart,
|
||||
Tag,
|
||||
CertificateInfo,
|
||||
PrismEditor,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -262,6 +310,13 @@ export default {
|
||||
chunksNavigation: "scroll",
|
||||
},
|
||||
cacheTime: Date.now(),
|
||||
importantHeartBeatListLength: 0,
|
||||
displayedRecords: [],
|
||||
pushMonitor: {
|
||||
showPushExamples: false,
|
||||
currentExample: "javascript-fetch",
|
||||
code: "",
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -300,16 +355,6 @@ export default {
|
||||
return this.$t("notAvailableShort");
|
||||
},
|
||||
|
||||
importantHeartBeatList() {
|
||||
if (this.$root.importantHeartbeatList[this.monitor.id]) {
|
||||
// eslint-disable-next-line vue/no-side-effects-in-computed-properties
|
||||
this.heartBeatList = this.$root.importantHeartbeatList[this.monitor.id];
|
||||
return this.$root.importantHeartbeatList[this.monitor.id];
|
||||
}
|
||||
|
||||
return [];
|
||||
},
|
||||
|
||||
status() {
|
||||
if (this.$root.statusList[this.monitor.id]) {
|
||||
return this.$root.statusList[this.monitor.id];
|
||||
@@ -333,12 +378,6 @@ export default {
|
||||
return this.tlsInfo != null && this.toggleCertInfoBox;
|
||||
},
|
||||
|
||||
displayedRecords() {
|
||||
const startIndex = this.perPage * (this.page - 1);
|
||||
const endIndex = startIndex + this.perPage;
|
||||
return this.heartBeatList.slice(startIndex, endIndex);
|
||||
},
|
||||
|
||||
group() {
|
||||
if (!this.monitor.pathName.includes("/")) {
|
||||
return "";
|
||||
@@ -354,73 +393,136 @@ export default {
|
||||
return getResBaseURL() + this.monitor.screenshot + "?time=" + this.cacheTime;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
||||
},
|
||||
methods: {
|
||||
getResBaseURL,
|
||||
/** Request a test notification be sent for this monitor */
|
||||
testNotification() {
|
||||
this.$root.getSocket().emit("testNotification", this.monitor.id);
|
||||
toast.success("Test notification is requested.");
|
||||
watch: {
|
||||
page(to) {
|
||||
this.getImportantHeartbeatListPaged();
|
||||
},
|
||||
|
||||
/** Show dialog to confirm pause */
|
||||
monitor(to) {
|
||||
this.getImportantHeartbeatListLength();
|
||||
},
|
||||
"monitor.type"() {
|
||||
if (this.monitor && this.monitor.type === "push") {
|
||||
this.loadPushExample();
|
||||
}
|
||||
},
|
||||
"pushMonitor.currentExample"() {
|
||||
this.loadPushExample();
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.getImportantHeartbeatListLength();
|
||||
|
||||
this.$root.emitter.on("newImportantHeartbeat", this.onNewImportantHeartbeat);
|
||||
|
||||
if (this.monitor && this.monitor.type === "push") {
|
||||
if (this.lastHeartBeat.status === -1) {
|
||||
this.pushMonitor.showPushExamples = true;
|
||||
}
|
||||
this.loadPushExample();
|
||||
}
|
||||
},
|
||||
|
||||
beforeUnmount() {
|
||||
this.$root.emitter.off("newImportantHeartbeat", this.onNewImportantHeartbeat);
|
||||
},
|
||||
|
||||
methods: {
|
||||
getResBaseURL,
|
||||
/**
|
||||
* Request a test notification be sent for this monitor
|
||||
* @returns {void}
|
||||
*/
|
||||
testNotification() {
|
||||
this.$root.getSocket().emit("testNotification", this.monitor.id);
|
||||
this.$root.toastSuccess("Test notification is requested.");
|
||||
},
|
||||
|
||||
/**
|
||||
* Show dialog to confirm pause
|
||||
* @returns {void}
|
||||
*/
|
||||
pauseDialog() {
|
||||
this.$refs.confirmPause.show();
|
||||
},
|
||||
|
||||
/** Resume this monitor */
|
||||
/**
|
||||
* Resume this monitor
|
||||
* @returns {void}
|
||||
*/
|
||||
resumeMonitor() {
|
||||
this.$root.getSocket().emit("resumeMonitor", this.monitor.id, (res) => {
|
||||
this.$root.toastRes(res);
|
||||
});
|
||||
},
|
||||
|
||||
/** Request that this monitor is paused */
|
||||
/**
|
||||
* Request that this monitor is paused
|
||||
* @returns {void}
|
||||
*/
|
||||
pauseMonitor() {
|
||||
this.$root.getSocket().emit("pauseMonitor", this.monitor.id, (res) => {
|
||||
this.$root.toastRes(res);
|
||||
});
|
||||
},
|
||||
|
||||
/** Show dialog to confirm deletion */
|
||||
/**
|
||||
* Show dialog to confirm deletion
|
||||
* @returns {void}
|
||||
*/
|
||||
deleteDialog() {
|
||||
this.$refs.confirmDelete.show();
|
||||
},
|
||||
|
||||
/** Show dialog to confirm clearing events */
|
||||
/**
|
||||
* Show dialog to confirm clearing events
|
||||
* @returns {void}
|
||||
*/
|
||||
clearEventsDialog() {
|
||||
this.$refs.confirmClearEvents.show();
|
||||
},
|
||||
|
||||
/** Show dialog to confirm clearing heartbeats */
|
||||
/**
|
||||
* Show dialog to confirm clearing heartbeats
|
||||
* @returns {void}
|
||||
*/
|
||||
clearHeartbeatsDialog() {
|
||||
this.$refs.confirmClearHeartbeats.show();
|
||||
},
|
||||
|
||||
/** Request that this monitor is deleted */
|
||||
/**
|
||||
* Request that this monitor is deleted
|
||||
* @returns {void}
|
||||
*/
|
||||
deleteMonitor() {
|
||||
this.$root.deleteMonitor(this.monitor.id, (res) => {
|
||||
this.$root.toastRes(res);
|
||||
if (res.ok) {
|
||||
toast.success(res.msg);
|
||||
this.$router.push("/dashboard");
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Request that this monitors events are cleared
|
||||
* @returns {void}
|
||||
*/
|
||||
clearEvents() {
|
||||
this.$root.clearEvents(this.monitor.id, (res) => {
|
||||
if (res.ok) {
|
||||
this.getImportantHeartbeatListLength();
|
||||
} else {
|
||||
toast.error(res.msg);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/** Request that this monitors events are cleared */
|
||||
clearEvents() {
|
||||
this.$root.clearEvents(this.monitor.id, (res) => {
|
||||
if (! res.ok) {
|
||||
toast.error(res.msg);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/** Request that this monitors heartbeats are cleared */
|
||||
/**
|
||||
* Request that this monitors heartbeats are cleared
|
||||
* @returns {void}
|
||||
*/
|
||||
clearHeartbeats() {
|
||||
this.$root.clearHeartbeats(this.monitor.id, (res) => {
|
||||
if (! res.ok) {
|
||||
@@ -431,8 +533,8 @@ export default {
|
||||
|
||||
/**
|
||||
* Return the correct title for the ping stat
|
||||
* @param {boolean} [average=false] Is the statistic an average?
|
||||
* @returns {string} Title formated dependant on monitor type
|
||||
* @param {boolean} average Is the statistic an average?
|
||||
* @returns {string} Title formatted dependant on monitor type
|
||||
*/
|
||||
pingTitle(average = false) {
|
||||
let translationPrefix = "";
|
||||
@@ -456,7 +558,11 @@ export default {
|
||||
return getMonitorRelativeURL(id);
|
||||
},
|
||||
|
||||
/** Filter and hide password in URL for display */
|
||||
/**
|
||||
* Filter and hide password in URL for display
|
||||
* @param {string} urlString URL to censor
|
||||
* @returns {string} Censored URL
|
||||
*/
|
||||
filterPassword(urlString) {
|
||||
try {
|
||||
let parsedUrl = new URL(urlString);
|
||||
@@ -468,6 +574,72 @@ export default {
|
||||
// Handle SQL Server
|
||||
return urlString.replaceAll(/Password=(.+);/ig, "Password=******;");
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieves the length of the important heartbeat list for this monitor.
|
||||
* @returns {void}
|
||||
*/
|
||||
getImportantHeartbeatListLength() {
|
||||
if (this.monitor) {
|
||||
this.$root.getSocket().emit("monitorImportantHeartbeatListCount", this.monitor.id, (res) => {
|
||||
if (res.ok) {
|
||||
this.importantHeartBeatListLength = res.count;
|
||||
this.getImportantHeartbeatListPaged();
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieves the important heartbeat list for the current page.
|
||||
* @returns {void}
|
||||
*/
|
||||
getImportantHeartbeatListPaged() {
|
||||
if (this.monitor) {
|
||||
const offset = (this.page - 1) * this.perPage;
|
||||
this.$root.getSocket().emit("monitorImportantHeartbeatListPaged", this.monitor.id, offset, this.perPage, (res) => {
|
||||
if (res.ok) {
|
||||
this.displayedRecords = res.data;
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the displayed records when a new important heartbeat arrives.
|
||||
* @param {object} heartbeat - The heartbeat object received.
|
||||
* @returns {void}
|
||||
*/
|
||||
onNewImportantHeartbeat(heartbeat) {
|
||||
if (heartbeat.monitorID === this.monitor?.id) {
|
||||
if (this.page === 1) {
|
||||
this.displayedRecords.unshift(heartbeat);
|
||||
if (this.displayedRecords.length > this.perPage) {
|
||||
this.displayedRecords.pop();
|
||||
}
|
||||
this.importantHeartBeatListLength += 1;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Highlight the example code
|
||||
* @param {string} code Code
|
||||
* @returns {string} Highlighted code
|
||||
*/
|
||||
pushExampleHighlighter(code) {
|
||||
return highlight(code, languages.js);
|
||||
},
|
||||
|
||||
loadPushExample() {
|
||||
this.pushMonitor.code = "";
|
||||
this.$root.getSocket().emit("getPushExample", this.pushMonitor.currentExample, (res) => {
|
||||
let code = res.code
|
||||
.replace("60", this.monitor.interval)
|
||||
.replace("https://example.com/api/push/key?status=up&msg=OK&ping=", this.pushURL);
|
||||
this.pushMonitor.code = code;
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
|
@@ -246,14 +246,11 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useToast } from "vue-toastification";
|
||||
import VueMultiselect from "vue-multiselect";
|
||||
import Datepicker from "@vuepic/vue-datepicker";
|
||||
import { timezoneList } from "../util-frontend";
|
||||
import cronstrue from "cronstrue/i18n";
|
||||
|
||||
const toast = useToast();
|
||||
|
||||
export default {
|
||||
components: {
|
||||
VueMultiselect,
|
||||
@@ -417,7 +414,10 @@ export default {
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
/** Initialise page */
|
||||
/**
|
||||
* Initialise page
|
||||
* @returns {void}
|
||||
*/
|
||||
init() {
|
||||
this.affectedMonitors = [];
|
||||
this.selectedStatusPages = [];
|
||||
@@ -454,7 +454,7 @@ export default {
|
||||
this.affectedMonitors.push(this.affectedMonitorsOptions.find(item => item.id === monitor.id));
|
||||
});
|
||||
} else {
|
||||
toast.error(res.msg);
|
||||
this.$root.toastError(res.msg);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -469,22 +469,25 @@ export default {
|
||||
|
||||
this.showOnAllPages = Object.values(res.statusPages).length === this.selectedStatusPagesOptions.length;
|
||||
} else {
|
||||
toast.error(res.msg);
|
||||
this.$root.toastError(res.msg);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
toast.error(res.msg);
|
||||
this.$root.toastError(res.msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/** Create new maintenance */
|
||||
/**
|
||||
* Create new maintenance
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async submit() {
|
||||
this.processing = true;
|
||||
|
||||
if (this.affectedMonitors.length === 0) {
|
||||
toast.error(this.$t("atLeastOneMonitor"));
|
||||
this.$root.toastError(this.$t("atLeastOneMonitor"));
|
||||
return this.processing = false;
|
||||
}
|
||||
|
||||
@@ -493,14 +496,14 @@ export default {
|
||||
if (res.ok) {
|
||||
await this.addMonitorMaintenance(res.maintenanceID, async () => {
|
||||
await this.addMaintenanceStatusPage(res.maintenanceID, () => {
|
||||
toast.success(res.msg);
|
||||
this.$root.toastRes(res);
|
||||
this.processing = false;
|
||||
this.$root.getMaintenanceList();
|
||||
this.$router.push("/maintenance");
|
||||
});
|
||||
});
|
||||
} else {
|
||||
toast.error(res.msg);
|
||||
this.$root.toastRes(res);
|
||||
this.processing = false;
|
||||
}
|
||||
|
||||
@@ -518,7 +521,7 @@ export default {
|
||||
});
|
||||
} else {
|
||||
this.processing = false;
|
||||
toast.error(res.msg);
|
||||
this.$root.toastError(res.msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -526,13 +529,14 @@ export default {
|
||||
|
||||
/**
|
||||
* Add monitor to maintenance
|
||||
* @param {number} maintenanceID
|
||||
* @param {socketCB} callback
|
||||
* @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) {
|
||||
toast.error(res.msg);
|
||||
this.$root.toastError(res.msg);
|
||||
} else {
|
||||
this.$root.getMonitorList();
|
||||
}
|
||||
@@ -543,13 +547,14 @@ export default {
|
||||
|
||||
/**
|
||||
* Add status page to maintenance
|
||||
* @param {number} maintenanceID
|
||||
* @param {socketCB} callback
|
||||
* @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) {
|
||||
toast.error(res.msg);
|
||||
this.$root.toastError(res.msg);
|
||||
} else {
|
||||
this.$root.getMaintenanceList();
|
||||
}
|
||||
|
@@ -119,6 +119,9 @@
|
||||
{{ $t("needPushEvery", [monitor.interval]) }}<br />
|
||||
{{ $t("pushOptionalParams", ["status, msg, ping"]) }}
|
||||
</div>
|
||||
<button class="btn btn-primary" type="button" @click="resetToken">
|
||||
{{ $t("Reset Token") }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Keyword -->
|
||||
@@ -658,6 +661,7 @@
|
||||
<label for="httpBodyEncoding" class="form-label">{{ $t("Body Encoding") }}</label>
|
||||
<select id="httpBodyEncoding" v-model="monitor.httpBodyEncoding" class="form-select">
|
||||
<option value="json">JSON</option>
|
||||
<option value="form">x-www-form-urlencoded</option>
|
||||
<option value="xml">XML</option>
|
||||
</select>
|
||||
</div>
|
||||
@@ -849,7 +853,9 @@ import { hostNameRegexPattern } from "../util-frontend";
|
||||
import { sleep } from "../util";
|
||||
import HiddenInput from "../components/HiddenInput.vue";
|
||||
|
||||
const toast = useToast();
|
||||
const toast = useToast;
|
||||
|
||||
const pushTokenLength = 32;
|
||||
|
||||
const monitorDefaults = {
|
||||
type: "http",
|
||||
@@ -1010,6 +1016,9 @@ message HealthCheckResponse {
|
||||
</soap:Body>
|
||||
</soap:Envelope>` ]);
|
||||
}
|
||||
if (this.monitor && this.monitor.httpBodyEncoding === "form") {
|
||||
return this.$t("Example:", [ "key1=value1&key2=value2" ]);
|
||||
}
|
||||
return this.$t("Example:", [ `
|
||||
{
|
||||
"key": "value"
|
||||
@@ -1077,8 +1086,7 @@ message HealthCheckResponse {
|
||||
|
||||
/**
|
||||
* Generates the parent monitor options list based on the sorted group monitor list and draft group name.
|
||||
*
|
||||
* @return {Array} The parent monitor options list.
|
||||
* @returns {Array} The parent monitor options list.
|
||||
*/
|
||||
parentMonitorOptionsList() {
|
||||
let list = [];
|
||||
@@ -1164,7 +1172,9 @@ message HealthCheckResponse {
|
||||
"monitor.type"() {
|
||||
if (this.monitor.type === "push") {
|
||||
if (! this.monitor.pushToken) {
|
||||
this.monitor.pushToken = genSecret(10);
|
||||
// ideally this would require checking if the generated token is already used
|
||||
// it's very unlikely to get a collision though (62^32 ~ 2.27265788 * 10^57 unique tokens)
|
||||
this.monitor.pushToken = genSecret(pushTokenLength);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1185,7 +1195,7 @@ message HealthCheckResponse {
|
||||
if (res.ok) {
|
||||
this.gameList = res.gameList;
|
||||
} else {
|
||||
toast.error(res.msg);
|
||||
this.$root.toastError(res.msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1257,7 +1267,10 @@ message HealthCheckResponse {
|
||||
this.kafkaSaslMechanismOptions = kafkaSaslMechanismOptions;
|
||||
},
|
||||
methods: {
|
||||
/** Initialize the edit monitor form */
|
||||
/**
|
||||
* Initialize the edit monitor form
|
||||
* @returns {void}
|
||||
*/
|
||||
init() {
|
||||
if (this.isAdd) {
|
||||
|
||||
@@ -1327,7 +1340,7 @@ message HealthCheckResponse {
|
||||
this.monitor.timeout = ~~(this.monitor.interval * 8) / 10;
|
||||
}
|
||||
} else {
|
||||
toast.error(res.msg);
|
||||
this.$root.toastError(res.msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1370,6 +1383,10 @@ message HealthCheckResponse {
|
||||
return true;
|
||||
},
|
||||
|
||||
resetToken() {
|
||||
this.monitor.pushToken = genSecret(pushTokenLength);
|
||||
},
|
||||
|
||||
/**
|
||||
* Submit the form data for processing
|
||||
* @returns {void}
|
||||
@@ -1423,7 +1440,7 @@ message HealthCheckResponse {
|
||||
createdNewParent = true;
|
||||
this.monitor.parent = res.monitorID;
|
||||
} else {
|
||||
toast.error(res.msg);
|
||||
this.$root.toastError(res.msg);
|
||||
this.processing = false;
|
||||
return;
|
||||
}
|
||||
@@ -1439,17 +1456,14 @@ message HealthCheckResponse {
|
||||
if (createdNewParent) {
|
||||
this.startParentGroupMonitor();
|
||||
}
|
||||
|
||||
toast.success(res.msg);
|
||||
this.processing = false;
|
||||
this.$root.getMonitorList();
|
||||
this.$router.push("/dashboard/" + res.monitorID);
|
||||
|
||||
} else {
|
||||
toast.error(res.msg);
|
||||
this.processing = false;
|
||||
}
|
||||
|
||||
this.$root.toastRes(res);
|
||||
});
|
||||
} else {
|
||||
await this.$refs.tagsManager.submit(this.monitor.id);
|
||||
@@ -1476,6 +1490,7 @@ message HealthCheckResponse {
|
||||
* Added a Notification Event
|
||||
* Enable it if the notification is added in EditMonitor.vue
|
||||
* @param {number} id ID of notification to add
|
||||
* @returns {void}
|
||||
*/
|
||||
addedNotification(id) {
|
||||
this.monitor.notificationIDList[id] = true;
|
||||
@@ -1485,21 +1500,26 @@ message HealthCheckResponse {
|
||||
* Added a Proxy Event
|
||||
* Enable it if the proxy is added in EditMonitor.vue
|
||||
* @param {number} id ID of proxy to add
|
||||
* @returns {void}
|
||||
*/
|
||||
addedProxy(id) {
|
||||
this.monitor.proxyId = id;
|
||||
},
|
||||
|
||||
// Added a Docker Host Event
|
||||
// Enable it if the Docker Host is added in EditMonitor.vue
|
||||
/**
|
||||
* Added a Docker Host Event
|
||||
* Enable it if the Docker Host is added in EditMonitor.vue
|
||||
* @param {number} id ID of docker host
|
||||
* @returns {void}
|
||||
*/
|
||||
addedDockerHost(id) {
|
||||
this.monitor.docker_host = id;
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds a draft group.
|
||||
*
|
||||
* @param {string} draftGroupName - The name of the draft group.
|
||||
* @param {string} draftGroupName The name of the draft group.
|
||||
* @returns {void}
|
||||
*/
|
||||
addedDraftGroup(draftGroupName) {
|
||||
this.draftGroupName = draftGroupName;
|
||||
|
@@ -19,25 +19,33 @@ export default {
|
||||
},
|
||||
async mounted() {
|
||||
|
||||
// There are only 2 cases that could come in here.
|
||||
// There are only 3 cases that could come in here.
|
||||
// 1. Matched status Page domain name
|
||||
// 2. Vue Frontend Dev
|
||||
let res = (await axios.get("/api/entry-page")).data;
|
||||
// 3. Vue Frontend Dev (not setup database yet)
|
||||
let res;
|
||||
try {
|
||||
res = (await axios.get("/api/entry-page")).data;
|
||||
|
||||
if (res.type === "statusPageMatchedDomain") {
|
||||
this.statusPageSlug = res.statusPageSlug;
|
||||
this.$root.forceStatusPageTheme = true;
|
||||
if (res.type === "statusPageMatchedDomain") {
|
||||
this.statusPageSlug = res.statusPageSlug;
|
||||
this.$root.forceStatusPageTheme = true;
|
||||
|
||||
} else if (res.type === "entryPage") { // Dev only. For production, the logic is in the server side
|
||||
const entryPage = res.entryPage;
|
||||
} else if (res.type === "entryPage") { // Dev only. For production, the logic is in the server side
|
||||
const entryPage = res.entryPage;
|
||||
|
||||
if (entryPage === "statusPage") {
|
||||
this.$router.push("/status");
|
||||
if (entryPage === "statusPage") {
|
||||
this.$router.push("/status");
|
||||
} else {
|
||||
this.$router.push("/dashboard");
|
||||
}
|
||||
} else if (res.type === "setup-database") {
|
||||
this.$router.push("/setup-database");
|
||||
} else {
|
||||
this.$router.push("/dashboard");
|
||||
}
|
||||
} else {
|
||||
this.$router.push("/dashboard");
|
||||
} catch (e) {
|
||||
alert("Cannot connect to the backend server. Did you start the backend server? (npm run start-server-dev)");
|
||||
}
|
||||
|
||||
},
|
||||
|
@@ -65,7 +65,10 @@ export default {
|
||||
this.init();
|
||||
},
|
||||
methods: {
|
||||
/** Initialise page */
|
||||
/**
|
||||
* Initialise page
|
||||
* @returns {void}
|
||||
*/
|
||||
init() {
|
||||
this.$root.getSocket().emit("getMonitorMaintenance", this.$route.params.id, (res) => {
|
||||
if (res.ok) {
|
||||
@@ -84,20 +87,22 @@ export default {
|
||||
});
|
||||
},
|
||||
|
||||
/** Confirm deletion */
|
||||
/**
|
||||
* Confirm deletion
|
||||
* @returns {void}
|
||||
*/
|
||||
deleteDialog() {
|
||||
this.$refs.confirmDelete.show();
|
||||
},
|
||||
|
||||
/** Delete maintenance after showing confirmation */
|
||||
/**
|
||||
* Delete maintenance after showing confirmation
|
||||
* @returns {void}
|
||||
*/
|
||||
deleteMaintenance() {
|
||||
this.$root.deleteMaintenance(this.maintenance.id, (res) => {
|
||||
if (res.ok) {
|
||||
toast.success(res.msg);
|
||||
this.$router.push("/maintenance");
|
||||
} else {
|
||||
toast.error(res.msg);
|
||||
}
|
||||
this.$root.toastRes(res);
|
||||
this.$router.push("/maintenance");
|
||||
});
|
||||
},
|
||||
},
|
||||
|
@@ -81,8 +81,6 @@ import { getResBaseURL } from "../util-frontend";
|
||||
import { getMaintenanceRelativeURL } from "../util.ts";
|
||||
import Confirm from "../components/Confirm.vue";
|
||||
import MaintenanceTime from "../components/MaintenanceTime.vue";
|
||||
import { useToast } from "vue-toastification";
|
||||
const toast = useToast();
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -135,7 +133,7 @@ export default {
|
||||
|
||||
/**
|
||||
* Get maintenance URL
|
||||
* @param {number} id
|
||||
* @param {number} id ID of maintenance to read
|
||||
* @returns {string} Relative URL
|
||||
*/
|
||||
maintenanceURL(id) {
|
||||
@@ -144,27 +142,33 @@ export default {
|
||||
|
||||
/**
|
||||
* Show delete confirmation
|
||||
* @param {number} maintenanceID
|
||||
* @param {number} maintenanceID ID of maintenance to show delete
|
||||
* confirmation for.
|
||||
* @returns {void}
|
||||
*/
|
||||
deleteDialog(maintenanceID) {
|
||||
this.selectedMaintenanceID = maintenanceID;
|
||||
this.$refs.confirmDelete.show();
|
||||
},
|
||||
|
||||
/** Delete maintenance after showing confirmation dialog */
|
||||
/**
|
||||
* Delete maintenance after showing confirmation dialog
|
||||
* @returns {void}
|
||||
*/
|
||||
deleteMaintenance() {
|
||||
this.$root.deleteMaintenance(this.selectedMaintenanceID, (res) => {
|
||||
this.$root.toastRes(res);
|
||||
if (res.ok) {
|
||||
toast.success(res.msg);
|
||||
this.$router.push("/maintenance");
|
||||
} else {
|
||||
toast.error(res.msg);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Show dialog to confirm pause
|
||||
* @param {number} maintenanceID ID of maintenance to confirm
|
||||
* pause.
|
||||
* @returns {void}
|
||||
*/
|
||||
pauseDialog(maintenanceID) {
|
||||
this.selectedMaintenanceID = maintenanceID;
|
||||
@@ -173,6 +177,7 @@ export default {
|
||||
|
||||
/**
|
||||
* Pause maintenance
|
||||
* @returns {void}
|
||||
*/
|
||||
pauseMaintenance() {
|
||||
this.$root.getSocket().emit("pauseMaintenance", this.selectedMaintenanceID, (res) => {
|
||||
@@ -182,6 +187,8 @@ export default {
|
||||
|
||||
/**
|
||||
* Resume maintenance
|
||||
* @param {number} id ID of maintenance to resume
|
||||
* @returns {void}
|
||||
*/
|
||||
resumeMaintenance(id) {
|
||||
this.$root.getSocket().emit("resumeMaintenance", id, (res) => {
|
||||
|
@@ -45,7 +45,10 @@ export default {
|
||||
|
||||
},
|
||||
methods: {
|
||||
/** Go back 1 in browser history */
|
||||
/**
|
||||
* Go back 1 in browser history
|
||||
* @returns {void}
|
||||
*/
|
||||
goBack() {
|
||||
history.back();
|
||||
}
|
||||
|
@@ -113,9 +113,6 @@ export default {
|
||||
proxies: {
|
||||
title: this.$t("Proxies"),
|
||||
},
|
||||
backup: {
|
||||
title: this.$t("Backup"),
|
||||
},
|
||||
about: {
|
||||
title: this.$t("About"),
|
||||
},
|
||||
@@ -139,6 +136,7 @@ export default {
|
||||
/**
|
||||
* Load the general settings page
|
||||
* For desktop only, on mobile do nothing
|
||||
* @returns {void}
|
||||
*/
|
||||
loadGeneralPage() {
|
||||
if (!this.currentPage && !this.$root.isMobile) {
|
||||
@@ -146,7 +144,10 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
/** Load settings from server */
|
||||
/**
|
||||
* Load settings from server
|
||||
* @returns {void}
|
||||
*/
|
||||
loadSettings() {
|
||||
this.$root.getSocket().emit("getSettings", (res) => {
|
||||
this.settings = res.data;
|
||||
@@ -190,13 +191,15 @@ export default {
|
||||
/**
|
||||
* Callback for saving settings
|
||||
* @callback saveSettingsCB
|
||||
* @param {Object} res Result of operation
|
||||
* @param {object} res Result of operation
|
||||
* @returns {void}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Save Settings
|
||||
* @param {saveSettingsCB} [callback]
|
||||
* @param {string} [currentPassword] Only need for disableAuth to true
|
||||
* @param {saveSettingsCB} callback Callback for socket response
|
||||
* @param {string} currentPassword Only need for disableAuth to true
|
||||
* @returns {void}
|
||||
*/
|
||||
saveSettings(callback, currentPassword) {
|
||||
let valid = this.validateSettings();
|
||||
@@ -216,7 +219,7 @@ export default {
|
||||
|
||||
/**
|
||||
* Ensure settings are valid
|
||||
* @returns {Object} Contains success state and error msg
|
||||
* @returns {object} Contains success state and error msg
|
||||
*/
|
||||
validateSettings() {
|
||||
if (this.settings.keepDataPeriodDays < 0) {
|
||||
|
@@ -46,9 +46,6 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useToast } from "vue-toastification";
|
||||
const toast = useToast();
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
@@ -62,6 +59,8 @@ export default {
|
||||
|
||||
},
|
||||
mounted() {
|
||||
// TODO: Check if it is a database setup
|
||||
|
||||
this.$root.getSocket().emit("needSetup", (needSetup) => {
|
||||
if (! needSetup) {
|
||||
this.$router.push("/");
|
||||
@@ -77,7 +76,7 @@ export default {
|
||||
this.processing = true;
|
||||
|
||||
if (this.password !== this.repeatPassword) {
|
||||
toast.error(this.$t("PasswordsDoNotMatch"));
|
||||
this.$root.toastError("PasswordsDoNotMatch");
|
||||
this.processing = false;
|
||||
return;
|
||||
}
|
||||
|
238
src/pages/SetupDatabase.vue
Normal file
238
src/pages/SetupDatabase.vue
Normal file
@@ -0,0 +1,238 @@
|
||||
<template>
|
||||
<div v-if="show" class="form-container">
|
||||
<form @submit.prevent="submit">
|
||||
<div>
|
||||
<object width="64" height="64" data="/icon.svg" />
|
||||
<div style="font-size: 28px; font-weight: bold; margin-top: 5px;">
|
||||
Uptime Kuma
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="info.runningSetup" class="mt-5">
|
||||
<div class="alert alert-success mx-3 px-4" role="alert">
|
||||
<div class="d-flex align-items-center">
|
||||
<strong>Setting up the database. It may take a while, please be patient.</strong>
|
||||
<div class="ms-3 pt-1">
|
||||
<div class="spinner-border" role="status" aria-hidden="true"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template v-if="!info.runningSetup">
|
||||
<div class="form-floating short mt-3">
|
||||
<select id="language" v-model="$root.language" class="form-select">
|
||||
<option v-for="(lang, i) in $i18n.availableLocales" :key="`Lang${i}`" :value="lang">
|
||||
{{ $i18n.messages[lang].languageName }}
|
||||
</option>
|
||||
</select>
|
||||
<label for="language" class="form-label">{{ $t("Language") }}</label>
|
||||
</div>
|
||||
|
||||
<p class="mt-5 short">
|
||||
{{ $t("setupDatabaseChooseDatabase") }}
|
||||
</p>
|
||||
|
||||
<div class="btn-group" role="group" aria-label="Basic radio toggle button group">
|
||||
<template v-if="info.isEnabledEmbeddedMariaDB">
|
||||
<input id="btnradio3" v-model="dbConfig.type" type="radio" class="btn-check" autocomplete="off" value="embedded-mariadb">
|
||||
|
||||
<label class="btn btn-outline-primary" for="btnradio3">
|
||||
Embedded MariaDB
|
||||
</label>
|
||||
</template>
|
||||
|
||||
<input id="btnradio2" v-model="dbConfig.type" type="radio" class="btn-check" autocomplete="off" value="mariadb">
|
||||
<label class="btn btn-outline-primary" for="btnradio2">
|
||||
MariaDB/MySQL
|
||||
</label>
|
||||
|
||||
<input id="btnradio1" v-model="dbConfig.type" type="radio" class="btn-check" autocomplete="off" value="sqlite">
|
||||
<label class="btn btn-outline-primary" for="btnradio1">
|
||||
SQLite
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div v-if="dbConfig.type === 'embedded-mariadb'" class="mt-3 short">
|
||||
{{ $t("setupDatabaseEmbeddedMariaDB") }}
|
||||
</div>
|
||||
|
||||
<div v-if="dbConfig.type === 'mariadb'" class="mt-3 short">
|
||||
{{ $t("setupDatabaseMariaDB") }}
|
||||
</div>
|
||||
|
||||
<div v-if="dbConfig.type === 'sqlite'" class="mt-3 short">
|
||||
{{ $t("setupDatabaseSQLite") }}
|
||||
</div>
|
||||
|
||||
<template v-if="dbConfig.type === 'mariadb'">
|
||||
<div class="form-floating mt-3 short">
|
||||
<input id="floatingInput" v-model="dbConfig.hostname" type="text" class="form-control" required>
|
||||
<label for="floatingInput">{{ $t("Hostname") }}</label>
|
||||
</div>
|
||||
|
||||
<div class="form-floating mt-3 short">
|
||||
<input id="floatingInput" v-model="dbConfig.port" type="text" class="form-control" required>
|
||||
<label for="floatingInput">{{ $t("Port") }}</label>
|
||||
</div>
|
||||
|
||||
<div class="form-floating mt-3 short">
|
||||
<input id="floatingInput" v-model="dbConfig.username" type="text" class="form-control" required>
|
||||
<label for="floatingInput">{{ $t("Username") }}</label>
|
||||
</div>
|
||||
|
||||
<div class="form-floating mt-3 short">
|
||||
<input id="floatingInput" v-model="dbConfig.password" type="password" class="form-control" required>
|
||||
<label for="floatingInput">{{ $t("Password") }}</label>
|
||||
</div>
|
||||
|
||||
<div class="form-floating mt-3 short">
|
||||
<input id="floatingInput" v-model="dbConfig.dbName" type="text" class="form-control" required>
|
||||
<label for="floatingInput">{{ $t("dbName") }}</label>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<button class="btn btn-primary mt-4 short" type="submit" :disabled="disabledButton">
|
||||
{{ $t("Next") }}
|
||||
</button>
|
||||
</template>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from "axios";
|
||||
import { useToast } from "vue-toastification";
|
||||
import { sleep } from "../util.ts";
|
||||
const toast = useToast();
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
dbConfig: {
|
||||
type: undefined,
|
||||
port: 3306,
|
||||
hostname: "",
|
||||
username: "",
|
||||
password: "",
|
||||
dbName: "kuma",
|
||||
},
|
||||
info: {
|
||||
needSetup: false,
|
||||
runningSetup: false,
|
||||
isEnabledEmbeddedMariaDB: false,
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
disabledButton() {
|
||||
return this.dbConfig.type === undefined || this.info.runningSetup;
|
||||
},
|
||||
},
|
||||
async mounted() {
|
||||
let res = await axios.get("/setup-database-info");
|
||||
this.info = res.data;
|
||||
|
||||
if (this.info && this.info.needSetup === false) {
|
||||
location.href = "/setup";
|
||||
} else {
|
||||
this.show = true;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async submit() {
|
||||
this.info.runningSetup = true;
|
||||
|
||||
try {
|
||||
await axios.post("/setup-database", {
|
||||
dbConfig: this.dbConfig,
|
||||
});
|
||||
await sleep(2000);
|
||||
await this.goToMainServerWhenReady();
|
||||
} catch (e) {
|
||||
toast.error(e.response.data);
|
||||
} finally {
|
||||
this.info.runningSetup = false;
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
async goToMainServerWhenReady() {
|
||||
try {
|
||||
console.log("Trying...");
|
||||
let res = await axios.get("/setup-database-info");
|
||||
if (res.data && res.data.needSetup === false) {
|
||||
this.show = false;
|
||||
location.href = "/setup";
|
||||
} else {
|
||||
if (res.data) {
|
||||
this.info = res.data;
|
||||
}
|
||||
throw new Error("not ready");
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("Not ready yet");
|
||||
await sleep(2000);
|
||||
await this.goToMainServerWhenReady();
|
||||
}
|
||||
},
|
||||
|
||||
test() {
|
||||
this.$root.toastError("not implemented");
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.form-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding-top: 40px;
|
||||
padding-bottom: 40px;
|
||||
}
|
||||
|
||||
.btn-group {
|
||||
label {
|
||||
width: 200px;
|
||||
line-height: 55px;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.form-floating {
|
||||
> .form-select {
|
||||
padding-left: 1.3rem;
|
||||
padding-top: 1.525rem;
|
||||
line-height: 1.35;
|
||||
|
||||
~ label {
|
||||
padding-left: 1.3rem;
|
||||
}
|
||||
}
|
||||
|
||||
> label {
|
||||
padding-left: 1.3rem;
|
||||
}
|
||||
|
||||
> .form-control {
|
||||
padding-left: 1.3rem;
|
||||
}
|
||||
}
|
||||
|
||||
.short {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
form {
|
||||
max-width: 800px;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
@@ -453,6 +453,7 @@ export default {
|
||||
|
||||
/**
|
||||
* If the monitor is added to public list, which will not be in this list.
|
||||
* @returns {object[]} List of monitors
|
||||
*/
|
||||
sortedMonitorList() {
|
||||
let result = [];
|
||||
@@ -597,7 +598,8 @@ export default {
|
||||
|
||||
/**
|
||||
* If connected to the socket and logged in, request private data of this statusPage
|
||||
* @param connected
|
||||
* @param {boolean} loggedIn Is the client logged in?
|
||||
* @returns {void}
|
||||
*/
|
||||
"$root.loggedIn"(loggedIn) {
|
||||
if (loggedIn) {
|
||||
@@ -612,7 +614,7 @@ export default {
|
||||
}
|
||||
|
||||
} else {
|
||||
toast.error(res.msg);
|
||||
this.$root.toastError(res.msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -620,6 +622,8 @@ export default {
|
||||
|
||||
/**
|
||||
* Selected a monitor and add to the list.
|
||||
* @param {object} monitor Monitor to add
|
||||
* @returns {void}
|
||||
*/
|
||||
selectedMonitor(monitor) {
|
||||
if (monitor) {
|
||||
@@ -726,7 +730,7 @@ export default {
|
||||
/**
|
||||
* Get status page data
|
||||
* It should be preloaded in window.preloadData
|
||||
* @returns {Promise<any>}
|
||||
* @returns {Promise<any>} Status page data
|
||||
*/
|
||||
getData: function () {
|
||||
if (window.preloadData) {
|
||||
@@ -741,13 +745,16 @@ export default {
|
||||
/**
|
||||
* Provide syntax highlighting for CSS
|
||||
* @param {string} code Text to highlight
|
||||
* @returns {string}
|
||||
* @returns {string} Highlighted CSS
|
||||
*/
|
||||
highlighter(code) {
|
||||
return highlight(code, languages.css);
|
||||
},
|
||||
|
||||
/** Update the heartbeat list and update favicon if neccessary */
|
||||
/**
|
||||
* Update the heartbeat list and update favicon if necessary
|
||||
* @returns {void}
|
||||
*/
|
||||
updateHeartbeatList() {
|
||||
// If editMode, it will use the data from websocket.
|
||||
if (! this.editMode) {
|
||||
@@ -795,7 +802,10 @@ export default {
|
||||
}, 1000);
|
||||
},
|
||||
|
||||
/** Enable editing mode */
|
||||
/**
|
||||
* Enable editing mode
|
||||
* @returns {void}
|
||||
*/
|
||||
edit() {
|
||||
if (this.hasToken) {
|
||||
this.$root.initSocketIO(true);
|
||||
@@ -807,7 +817,10 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
/** Save the status page */
|
||||
/**
|
||||
* Save the status page
|
||||
* @returns {void}
|
||||
*/
|
||||
save() {
|
||||
this.loading = true;
|
||||
let startTime = new Date();
|
||||
@@ -838,33 +851,42 @@ export default {
|
||||
});
|
||||
},
|
||||
|
||||
/** Show dialog confirming deletion */
|
||||
/**
|
||||
* Show dialog confirming deletion
|
||||
* @returns {void}
|
||||
*/
|
||||
deleteDialog() {
|
||||
this.$refs.confirmDelete.show();
|
||||
},
|
||||
|
||||
/** Request deletion of this status page */
|
||||
/**
|
||||
* Request deletion of this status page
|
||||
* @returns {void}
|
||||
*/
|
||||
deleteStatusPage() {
|
||||
this.$root.getSocket().emit("deleteStatusPage", this.slug, (res) => {
|
||||
if (res.ok) {
|
||||
this.enableEditMode = false;
|
||||
location.href = "/manage-status-page";
|
||||
} else {
|
||||
toast.error(res.msg);
|
||||
this.$root.toastError(res.msg);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns label for a specifed monitor
|
||||
* @param {Object} monitor Object representing monitor
|
||||
* @returns {string}
|
||||
* Returns label for a specified monitor
|
||||
* @param {object} monitor Object representing monitor
|
||||
* @returns {string} Monitor label
|
||||
*/
|
||||
monitorSelectorLabel(monitor) {
|
||||
return `${monitor.name}`;
|
||||
},
|
||||
|
||||
/** Add a group to the status page */
|
||||
/**
|
||||
* Add a group to the status page
|
||||
* @returns {void}
|
||||
*/
|
||||
addGroup() {
|
||||
let groupName = this.$t("Untitled Group");
|
||||
|
||||
@@ -878,12 +900,18 @@ export default {
|
||||
});
|
||||
},
|
||||
|
||||
/** Add a domain to the status page */
|
||||
/**
|
||||
* Add a domain to the status page
|
||||
* @returns {void}
|
||||
*/
|
||||
addDomainField() {
|
||||
this.config.domainNameList.push("");
|
||||
},
|
||||
|
||||
/** Discard changes to status page */
|
||||
/**
|
||||
* Discard changes to status page
|
||||
* @returns {void}
|
||||
*/
|
||||
discard() {
|
||||
location.href = "/status/" + this.slug;
|
||||
},
|
||||
@@ -891,19 +919,26 @@ export default {
|
||||
/**
|
||||
* Set URL of new image after successful crop operation
|
||||
* @param {string} imgDataUrl URL of image in data:// format
|
||||
* @returns {void}
|
||||
*/
|
||||
cropSuccess(imgDataUrl) {
|
||||
this.imgDataUrl = imgDataUrl;
|
||||
},
|
||||
|
||||
/** Show image crop dialog if in edit mode */
|
||||
/**
|
||||
* Show image crop dialog if in edit mode
|
||||
* @returns {void}
|
||||
*/
|
||||
showImageCropUploadMethod() {
|
||||
if (this.editMode) {
|
||||
this.showImageCropUpload = true;
|
||||
}
|
||||
},
|
||||
|
||||
/** Create an incident for this status page */
|
||||
/**
|
||||
* Create an incident for this status page
|
||||
* @returns {void}
|
||||
*/
|
||||
createIncident() {
|
||||
this.enableEditIncidentMode = true;
|
||||
|
||||
@@ -918,10 +953,13 @@ export default {
|
||||
};
|
||||
},
|
||||
|
||||
/** Post the incident to the status page */
|
||||
/**
|
||||
* Post the incident to the status page
|
||||
* @returns {void}
|
||||
*/
|
||||
postIncident() {
|
||||
if (this.incident.title === "" || this.incident.content === "") {
|
||||
toast.error(this.$t("Please input title and content"));
|
||||
this.$root.toastError("Please input title and content");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -931,20 +969,26 @@ export default {
|
||||
this.enableEditIncidentMode = false;
|
||||
this.incident = res.incident;
|
||||
} else {
|
||||
toast.error(res.msg);
|
||||
this.$root.toastError(res.msg);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
/** Click Edit Button */
|
||||
/**
|
||||
* Click Edit Button
|
||||
* @returns {void}
|
||||
*/
|
||||
editIncident() {
|
||||
this.enableEditIncidentMode = true;
|
||||
this.previousIncident = Object.assign({}, this.incident);
|
||||
},
|
||||
|
||||
/** Cancel creation or editing of incident */
|
||||
/**
|
||||
* Cancel creation or editing of incident
|
||||
* @returns {void}
|
||||
*/
|
||||
cancelIncident() {
|
||||
this.enableEditIncidentMode = false;
|
||||
|
||||
@@ -954,7 +998,10 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
/** Unpin the incident */
|
||||
/**
|
||||
* Unpin the incident
|
||||
* @returns {void}
|
||||
*/
|
||||
unpinIncident() {
|
||||
this.$root.getSocket().emit("unpinIncident", this.slug, () => {
|
||||
this.incident = null;
|
||||
@@ -963,7 +1010,8 @@ export default {
|
||||
|
||||
/**
|
||||
* Get the relative time difference of a date from now
|
||||
* @returns {string}
|
||||
* @param {any} date Date to get time difference
|
||||
* @returns {string} Time difference
|
||||
*/
|
||||
dateFromNow(date) {
|
||||
return dayjs.utc(date).fromNow();
|
||||
@@ -972,6 +1020,7 @@ export default {
|
||||
/**
|
||||
* Remove a domain from the status page
|
||||
* @param {number} index Index of domain to remove
|
||||
* @returns {void}
|
||||
*/
|
||||
removeDomain(index) {
|
||||
this.config.domainNameList.splice(index, 1);
|
||||
@@ -979,7 +1028,7 @@ export default {
|
||||
|
||||
/**
|
||||
* Generate sanitized HTML from maintenance description
|
||||
* @param {string} description
|
||||
* @param {string} description Text to sanitize
|
||||
* @returns {string} Sanitized HTML
|
||||
*/
|
||||
maintenanceHTML(description) {
|
||||
@@ -1196,20 +1245,6 @@ footer {
|
||||
}
|
||||
}
|
||||
|
||||
/* required class */
|
||||
.css-editor {
|
||||
/* we dont use `language-` classes anymore so thats why we need to add background and text color manually */
|
||||
|
||||
border-radius: 1rem;
|
||||
padding: 10px 5px;
|
||||
border: 1px solid #ced4da;
|
||||
|
||||
.dark & {
|
||||
background: $dark-bg;
|
||||
border: 1px solid $dark-border-color;
|
||||
}
|
||||
}
|
||||
|
||||
.bg-maintenance {
|
||||
.alert-heading {
|
||||
font-weight: bold;
|
||||
|
Reference in New Issue
Block a user