mirror of
https://github.com/louislam/uptime-kuma.git
synced 2025-08-21 20:02:29 +08:00
Merge branch 'master' into introduce-resend-interval
This commit is contained in:
@@ -21,7 +21,9 @@
|
||||
<div class="form-text">
|
||||
<ul>
|
||||
<li>{{ $t("Accept characters:") }} <mark>a-z</mark> <mark>0-9</mark> <mark>-</mark></li>
|
||||
<li>{{ $t("Start or end with") }} <mark>a-z</mark> <mark>0-9</mark> only</li>
|
||||
<i18n-t tag="li" keypath="startOrEndWithOnly">
|
||||
<mark>a-z</mark> <mark>0-9</mark>
|
||||
</i18n-t>
|
||||
<li>{{ $t("No consecutive dashes") }} <mark>--</mark></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
@@ -25,9 +25,9 @@ export default {
|
||||
MonitorList,
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
return {};
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@@ -118,6 +118,7 @@ export default {
|
||||
return 0;
|
||||
});
|
||||
|
||||
// eslint-disable-next-line vue/no-side-effects-in-computed-properties
|
||||
this.heartBeatList = result;
|
||||
|
||||
return result;
|
||||
|
@@ -32,6 +32,9 @@
|
||||
<option value="steam">
|
||||
Steam Game Server
|
||||
</option>
|
||||
<option value="mqtt">
|
||||
MQTT
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
@@ -67,15 +70,15 @@
|
||||
</div>
|
||||
|
||||
<!-- Hostname -->
|
||||
<!-- TCP Port / Ping / DNS / Steam only -->
|
||||
<div v-if="monitor.type === 'port' || monitor.type === 'ping' || monitor.type === 'dns' || monitor.type === 'steam'" class="my-3">
|
||||
<!-- TCP Port / Ping / DNS / Steam / MQTT only -->
|
||||
<div v-if="monitor.type === 'port' || monitor.type === 'ping' || monitor.type === 'dns' || monitor.type === 'steam' || monitor.type === 'mqtt'" class="my-3">
|
||||
<label for="hostname" class="form-label">{{ $t("Hostname") }}</label>
|
||||
<input id="hostname" v-model="monitor.hostname" type="text" class="form-control" :pattern="`${ipRegexPattern}|${hostnameRegexPattern}`" required>
|
||||
</div>
|
||||
|
||||
<!-- Port -->
|
||||
<!-- For TCP Port / Steam Type -->
|
||||
<div v-if="monitor.type === 'port' || monitor.type === 'steam'" class="my-3">
|
||||
<!-- For TCP Port / Steam / MQTT Type -->
|
||||
<div v-if="monitor.type === 'port' || monitor.type === 'steam' || monitor.type === 'mqtt'" class="my-3">
|
||||
<label for="port" class="form-label">{{ $t("Port") }}</label>
|
||||
<input id="port" v-model="monitor.port" type="number" class="form-control" required min="0" max="65535" step="1">
|
||||
</div>
|
||||
@@ -115,6 +118,36 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- MQTT -->
|
||||
<!-- For MQTT Type -->
|
||||
<template v-if="monitor.type === 'mqtt'">
|
||||
<div class="my-3">
|
||||
<label for="mqttUsername" class="form-label">MQTT {{ $t("Username") }}</label>
|
||||
<input id="mqttUsername" v-model="monitor.mqttUsername" type="text" class="form-control">
|
||||
</div>
|
||||
|
||||
<div class="my-3">
|
||||
<label for="mqttPassword" class="form-label">MQTT {{ $t("Password") }}</label>
|
||||
<input id="mqttPassword" v-model="monitor.mqttPassword" type="password" class="form-control">
|
||||
</div>
|
||||
|
||||
<div class="my-3">
|
||||
<label for="mqttTopic" class="form-label">MQTT {{ $t("Topic") }}</label>
|
||||
<input id="mqttTopic" v-model="monitor.mqttTopic" type="text" class="form-control" required>
|
||||
<div class="form-text">
|
||||
{{ $t("topicExplanation") }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="my-3">
|
||||
<label for="mqttSuccessMessage" class="form-label">MQTT {{ $t("successMessage") }}</label>
|
||||
<input id="mqttSuccessMessage" v-model="monitor.mqttSuccessMessage" type="text" class="form-control">
|
||||
<div class="form-text">
|
||||
{{ $t("successMessageExplanation") }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Interval -->
|
||||
<div class="my-3">
|
||||
<label for="interval" class="form-label">{{ $t("Heartbeat Interval") }} ({{ $t("checkEverySecond", [ monitor.interval ]) }})</label>
|
||||
@@ -148,6 +181,15 @@
|
||||
|
||||
<h2 v-if="monitor.type !== 'push'" class="mt-5 mb-2">{{ $t("Advanced") }}</h2>
|
||||
|
||||
<div v-if="monitor.type === 'http' || monitor.type === 'keyword' " class="my-3 form-check">
|
||||
<input id="expiry-notification" v-model="monitor.expiryNotification" class="form-check-input" type="checkbox">
|
||||
<label class="form-check-label" for="expiry-notification">
|
||||
{{ $t("Domain Name Expiry Notification") }}
|
||||
</label>
|
||||
<div class="form-text">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="monitor.type === 'http' || monitor.type === 'keyword' " class="my-3 form-check">
|
||||
<input id="ignore-tls" v-model="monitor.ignoreTls" class="form-check-input" type="checkbox" value="">
|
||||
<label class="form-check-label" for="ignore-tls">
|
||||
@@ -231,6 +273,34 @@
|
||||
{{ $t("Setup Notification") }}
|
||||
</button>
|
||||
|
||||
<!-- Proxies -->
|
||||
<div v-if="monitor.type === 'http' || monitor.type === 'keyword'">
|
||||
<h2 class="mt-5 mb-2">{{ $t("Proxy") }}</h2>
|
||||
<p v-if="$root.proxyList.length === 0">
|
||||
{{ $t("Not available, please setup.") }}
|
||||
</p>
|
||||
|
||||
<div v-if="$root.proxyList.length > 0" class="form-check my-3">
|
||||
<input id="proxy-disable" v-model="monitor.proxyId" :value="null" name="proxy" class="form-check-input" type="radio">
|
||||
<label class="form-check-label" for="proxy-disable">{{ $t("No Proxy") }}</label>
|
||||
</div>
|
||||
|
||||
<div v-for="proxy in $root.proxyList" :key="proxy.id" class="form-check my-3">
|
||||
<input :id="`proxy-${proxy.id}`" v-model="monitor.proxyId" :value="proxy.id" name="proxy" class="form-check-input" type="radio">
|
||||
|
||||
<label class="form-check-label" :for="`proxy-${proxy.id}`">
|
||||
{{ proxy.host }}:{{ proxy.port }} ({{ proxy.protocol }})
|
||||
<a href="#" @click="$refs.proxyDialog.show(proxy.id)">{{ $t("Edit") }}</a>
|
||||
</label>
|
||||
|
||||
<span v-if="proxy.default === true" class="badge bg-primary ms-2">{{ $t("default") }}</span>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-primary me-2" type="button" @click="$refs.proxyDialog.show()">
|
||||
{{ $t("Setup Proxy") }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- HTTP Options -->
|
||||
<template v-if="monitor.type === 'http' || monitor.type === 'keyword' ">
|
||||
<h2 class="mt-5 mb-2">{{ $t("HTTP Options") }}</h2>
|
||||
@@ -294,12 +364,14 @@
|
||||
</form>
|
||||
|
||||
<NotificationDialog ref="notificationDialog" @added="addedNotification" />
|
||||
<ProxyDialog ref="proxyDialog" @added="addedProxy" />
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import NotificationDialog from "../components/NotificationDialog.vue";
|
||||
import ProxyDialog from "../components/ProxyDialog.vue";
|
||||
import TagsManager from "../components/TagsManager.vue";
|
||||
import CopyableInput from "../components/CopyableInput.vue";
|
||||
|
||||
@@ -311,6 +383,7 @@ const toast = useToast();
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ProxyDialog,
|
||||
CopyableInput,
|
||||
NotificationDialog,
|
||||
TagsManager,
|
||||
@@ -362,21 +435,32 @@ export default {
|
||||
},
|
||||
|
||||
bodyPlaceholder() {
|
||||
return this.$t("Example:", [`
|
||||
return this.$t("Example:", [ `
|
||||
{
|
||||
"key": "value"
|
||||
}`]);
|
||||
}` ]);
|
||||
},
|
||||
|
||||
headersPlaceholder() {
|
||||
return this.$t("Example:", [`
|
||||
return this.$t("Example:", [ `
|
||||
{
|
||||
"HeaderName": "HeaderValue"
|
||||
}`]);
|
||||
}` ]);
|
||||
}
|
||||
|
||||
},
|
||||
watch: {
|
||||
"$root.proxyList"() {
|
||||
if (this.isAdd) {
|
||||
if (this.$root.proxyList && !this.monitor.proxyId) {
|
||||
const proxy = this.$root.proxyList.find(proxy => proxy.default);
|
||||
|
||||
if (proxy) {
|
||||
this.monitor.proxyId = proxy.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"$route.fullPath"() {
|
||||
this.init();
|
||||
@@ -445,12 +529,26 @@ export default {
|
||||
notificationIDList: {},
|
||||
ignoreTls: false,
|
||||
upsideDown: false,
|
||||
expiryNotification: false,
|
||||
maxredirects: 10,
|
||||
accepted_statuscodes: ["200-299"],
|
||||
accepted_statuscodes: [ "200-299" ],
|
||||
dns_resolve_type: "A",
|
||||
dns_resolve_server: "1.1.1.1",
|
||||
proxyId: null,
|
||||
mqttUsername: "",
|
||||
mqttPassword: "",
|
||||
mqttTopic: "",
|
||||
mqttSuccessMessage: "",
|
||||
};
|
||||
|
||||
if (this.$root.proxyList && !this.monitor.proxyId) {
|
||||
const proxy = this.$root.proxyList.find(proxy => proxy.default);
|
||||
|
||||
if (proxy) {
|
||||
this.monitor.proxyId = proxy.id;
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < this.$root.notificationList.length; i++) {
|
||||
if (this.$root.notificationList[i].isDefault == true) {
|
||||
this.monitor.notificationIDList[this.$root.notificationList[i].id] = true;
|
||||
@@ -542,6 +640,12 @@ export default {
|
||||
addedNotification(id) {
|
||||
this.monitor.notificationIDList[id] = true;
|
||||
},
|
||||
|
||||
// Added a Proxy Event
|
||||
// Enable it if the proxy is added in EditMonitor.vue
|
||||
addedProxy(id) {
|
||||
this.monitor.proxyId = id;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@@ -1,19 +1,45 @@
|
||||
<template>
|
||||
<div></div>
|
||||
<div>
|
||||
<StatusPage v-if="statusPageSlug" :override-slug="statusPageSlug" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from "axios";
|
||||
import StatusPage from "./StatusPage.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
StatusPage,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
statusPageSlug: null,
|
||||
};
|
||||
},
|
||||
async mounted() {
|
||||
let entryPage = (await axios.get("/api/entry-page")).data;
|
||||
|
||||
if (entryPage === "statusPage") {
|
||||
this.$router.push("/status");
|
||||
// There are only 2 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;
|
||||
|
||||
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;
|
||||
|
||||
if (entryPage === "statusPage") {
|
||||
this.$router.push("/status");
|
||||
} else {
|
||||
this.$router.push("/dashboard");
|
||||
}
|
||||
} else {
|
||||
this.$router.push("/dashboard");
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
};
|
||||
|
@@ -11,6 +11,6 @@ export default {
|
||||
components: {
|
||||
MonitorList,
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
|
@@ -12,7 +12,7 @@
|
||||
<div class="shadow-box">
|
||||
<template v-if="$root.statusPageListLoaded">
|
||||
<span v-if="Object.keys($root.statusPageList).length === 0" class="d-flex align-items-center justify-content-center my-3">
|
||||
No status pages
|
||||
{{ $t("No status pages") }}
|
||||
</span>
|
||||
|
||||
<!-- use <a> instead of <router-link>, because the heartbeat won't load. -->
|
||||
@@ -92,7 +92,6 @@ export default {
|
||||
}
|
||||
|
||||
.info {
|
||||
|
||||
.title {
|
||||
font-weight: bold;
|
||||
font-size: 20px;
|
||||
|
@@ -16,6 +16,14 @@
|
||||
{{ item.title }}
|
||||
</div>
|
||||
</router-link>
|
||||
|
||||
<!-- Logout Button -->
|
||||
<a v-if="$root.isMobile && $root.loggedIn && $root.socket.token !== 'autoLogin'" class="logout" @click.prevent="$root.logout">
|
||||
<div class="menu-item">
|
||||
<font-awesome-icon icon="sign-out-alt" />
|
||||
{{ $t("Logout") }}
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="settings-content col-lg-9 col-md-7">
|
||||
<div v-if="currentPage" class="settings-content-header">
|
||||
@@ -75,12 +83,18 @@ export default {
|
||||
notifications: {
|
||||
title: this.$t("Notifications"),
|
||||
},
|
||||
"reverse-proxy": {
|
||||
title: this.$t("Reverse Proxy"),
|
||||
},
|
||||
"monitor-history": {
|
||||
title: this.$t("Monitor History"),
|
||||
},
|
||||
security: {
|
||||
title: this.$t("Security"),
|
||||
},
|
||||
proxies: {
|
||||
title: this.$t("Proxies"),
|
||||
},
|
||||
backup: {
|
||||
title: this.$t("Backup"),
|
||||
},
|
||||
@@ -115,6 +129,10 @@ export default {
|
||||
this.$root.getSocket().emit("getSettings", (res) => {
|
||||
this.settings = res.data;
|
||||
|
||||
if (this.settings.checkUpdate === undefined) {
|
||||
this.settings.checkUpdate = true;
|
||||
}
|
||||
|
||||
if (this.settings.searchEngineIndex === undefined) {
|
||||
this.settings.searchEngineIndex = false;
|
||||
}
|
||||
@@ -131,10 +149,18 @@ export default {
|
||||
});
|
||||
},
|
||||
|
||||
saveSettings() {
|
||||
this.$root.getSocket().emit("setSettings", this.settings, (res) => {
|
||||
/**
|
||||
* Save Settings
|
||||
* @param currentPassword (Optional) Only need for disableAuth to true
|
||||
*/
|
||||
saveSettings(callback, currentPassword) {
|
||||
this.$root.getSocket().emit("setSettings", this.settings, currentPassword, (res) => {
|
||||
this.$root.toastRes(res);
|
||||
this.loadSettings();
|
||||
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
});
|
||||
},
|
||||
}
|
||||
@@ -215,4 +241,8 @@ footer {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.logout {
|
||||
color: $danger !important;
|
||||
}
|
||||
</style>
|
||||
|
@@ -2,49 +2,80 @@
|
||||
<div v-if="loadedTheme" class="container mt-3">
|
||||
<!-- Sidebar for edit mode -->
|
||||
<div v-if="enableEditMode" class="sidebar">
|
||||
<div class="my-3">
|
||||
<label for="slug" class="form-label">{{ $t("Slug") }}</label>
|
||||
<div class="input-group">
|
||||
<span id="basic-addon3" class="input-group-text">/status/</span>
|
||||
<input id="slug" v-model="config.slug" type="text" class="form-control">
|
||||
<div class="sidebar-body">
|
||||
<div class="my-3">
|
||||
<label for="slug" class="form-label">{{ $t("Slug") }}</label>
|
||||
<div class="input-group">
|
||||
<span id="basic-addon3" class="input-group-text">/status/</span>
|
||||
<input id="slug" v-model="config.slug" type="text" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="my-3">
|
||||
<label for="title" class="form-label">{{ $t("Title") }}</label>
|
||||
<input id="title" v-model="config.title" type="text" class="form-control">
|
||||
</div>
|
||||
<div class="my-3">
|
||||
<label for="title" class="form-label">{{ $t("Title") }}</label>
|
||||
<input id="title" v-model="config.title" type="text" class="form-control">
|
||||
</div>
|
||||
|
||||
<div class="my-3">
|
||||
<label for="description" class="form-label">{{ $t("Description") }}</label>
|
||||
<textarea id="description" v-model="config.description" class="form-control"></textarea>
|
||||
</div>
|
||||
<!-- Description -->
|
||||
<div class="my-3">
|
||||
<label for="description" class="form-label">{{ $t("Description") }}</label>
|
||||
<textarea id="description" v-model="config.description" class="form-control"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="my-3 form-check form-switch">
|
||||
<input id="switch-theme" v-model="config.theme" class="form-check-input" type="checkbox" true-value="dark" false-value="light">
|
||||
<label class="form-check-label" for="switch-theme">{{ $t("Switch to Dark Theme") }}</label>
|
||||
</div>
|
||||
<!-- Footer Text -->
|
||||
<div class="my-3">
|
||||
<label for="footer-text" class="form-label">{{ $t("Footer Text") }}</label>
|
||||
<textarea id="footer-text" v-model="config.footerText" class="form-control"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="my-3 form-check form-switch">
|
||||
<input id="showTags" v-model="config.showTags" class="form-check-input" type="checkbox">
|
||||
<label class="form-check-label" for="showTags">{{ $t("Show Tags") }}</label>
|
||||
</div>
|
||||
<div class="my-3 form-check form-switch">
|
||||
<input id="switch-theme" v-model="config.theme" class="form-check-input" type="checkbox" true-value="dark" false-value="light">
|
||||
<label class="form-check-label" for="switch-theme">{{ $t("Switch to Dark Theme") }}</label>
|
||||
</div>
|
||||
|
||||
<div v-if="false" class="my-3">
|
||||
<label for="password" class="form-label">{{ $t("Password") }} <sup>Coming Soon</sup></label>
|
||||
<input id="password" v-model="config.password" disabled type="password" autocomplete="new-password" class="form-control">
|
||||
</div>
|
||||
<div class="my-3 form-check form-switch">
|
||||
<input id="showTags" v-model="config.showTags" class="form-check-input" type="checkbox">
|
||||
<label class="form-check-label" for="showTags">{{ $t("Show Tags") }}</label>
|
||||
</div>
|
||||
|
||||
<div v-if="false" class="my-3">
|
||||
<label for="cname" class="form-label">Domain Names <sup>Coming Soon</sup></label>
|
||||
<textarea id="cname" v-model="config.domanNames" rows="3" disabled class="form-control" :placeholder="domainNamesPlaceholder"></textarea>
|
||||
</div>
|
||||
<!-- Show Powered By -->
|
||||
<div class="my-3 form-check form-switch">
|
||||
<input id="show-powered-by" v-model="config.showPoweredBy" class="form-check-input" type="checkbox">
|
||||
<label class="form-check-label" for="show-powered-by">{{ $t("Show Powered By") }}</label>
|
||||
</div>
|
||||
|
||||
<div class="danger-zone">
|
||||
<button class="btn btn-danger me-2" @click="deleteDialog">
|
||||
<font-awesome-icon icon="trash" />
|
||||
{{ $t("Delete") }}
|
||||
</button>
|
||||
<div v-if="false" class="my-3">
|
||||
<label for="password" class="form-label">{{ $t("Password") }} <sup>Coming Soon</sup></label>
|
||||
<input id="password" v-model="config.password" disabled type="password" autocomplete="new-password" class="form-control">
|
||||
</div>
|
||||
|
||||
<!-- Domain Name List -->
|
||||
<div class="my-3">
|
||||
<label class="form-label">
|
||||
{{ $t("Domain Names") }}
|
||||
<font-awesome-icon icon="plus-circle" class="btn-add-domain action text-primary" @click="addDomainField" />
|
||||
</label>
|
||||
|
||||
<ul class="list-group domain-name-list">
|
||||
<li v-for="(domain, index) in config.domainNameList" :key="index" class="list-group-item">
|
||||
<input v-model="config.domainNameList[index]" type="text" class="no-bg domain-input" placeholder="example.com" />
|
||||
<font-awesome-icon icon="times" class="action remove ms-2 me-3 text-danger" @click="removeDomain(index)" />
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Custom CSS -->
|
||||
<div class="my-3">
|
||||
<div class="mb-1">{{ $t("Custom CSS") }}</div>
|
||||
<prism-editor v-model="config.customCSS" class="css-editor" :highlight="highlighter" line-numbers></prism-editor>
|
||||
</div>
|
||||
|
||||
<div class="danger-zone">
|
||||
<button class="btn btn-danger me-2" @click="deleteDialog">
|
||||
<font-awesome-icon icon="trash" />
|
||||
{{ $t("Delete") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sidebar Footer -->
|
||||
@@ -55,7 +86,7 @@
|
||||
</button>
|
||||
|
||||
<button class="btn btn-danger me-2" @click="discard">
|
||||
<font-awesome-icon icon="save" />
|
||||
<font-awesome-icon icon="undo" />
|
||||
{{ $t("Discard") }}
|
||||
</button>
|
||||
</div>
|
||||
@@ -67,7 +98,7 @@
|
||||
<h1 class="mb-4 title-flex">
|
||||
<!-- Logo -->
|
||||
<span class="logo-wrapper" @click="showImageCropUploadMethod">
|
||||
<img :src="logoURL" alt class="logo me-2" :class="logoClass" />
|
||||
<img :src="logoURL" alt class="logo me-2" :class="logoClass" @load="statusPageLogoLoaded" />
|
||||
<font-awesome-icon v-if="enableEditMode" class="icon-upload" icon="upload" />
|
||||
</span>
|
||||
|
||||
@@ -120,7 +151,7 @@
|
||||
|
||||
<!-- Incident Date -->
|
||||
<div class="date mt-3">
|
||||
{{ $t("Created") }}: {{ $root.datetime(incident.createdDate) }} ({{ dateFromNow(incident.createdDate) }})<br />
|
||||
{{ $t("Date Created") }}: {{ $root.datetime(incident.createdDate) }} ({{ dateFromNow(incident.createdDate) }})<br />
|
||||
<span v-if="incident.lastUpdatedDate">
|
||||
{{ $t("Last Updated") }}: {{ $root.datetime(incident.lastUpdatedDate) }} ({{ dateFromNow(incident.lastUpdatedDate) }})
|
||||
</span>
|
||||
@@ -227,13 +258,24 @@
|
||||
</div>
|
||||
|
||||
<footer class="mt-5 mb-4">
|
||||
{{ $t("Powered by") }} <a target="_blank" href="https://github.com/louislam/uptime-kuma">{{ $t("Uptime Kuma" ) }}</a>
|
||||
<div class="custom-footer-text text-start">
|
||||
<strong v-if="enableEditMode">{{ $t("Custom Footer") }}:</strong>
|
||||
</div>
|
||||
<Editable v-model="config.footerText" tag="div" :contenteditable="enableEditMode" :noNL="false" class="alert-heading p-2" />
|
||||
|
||||
<p v-if="config.showPoweredBy">
|
||||
{{ $t("Powered by") }} <a target="_blank" href="https://github.com/louislam/uptime-kuma">{{ $t("Uptime Kuma" ) }}</a>
|
||||
</p>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<Confirm ref="confirmDelete" btn-style="btn-danger" :yes-text="$t('Yes')" :no-text="$t('No')" @yes="deleteStatusPage">
|
||||
{{ $t("deleteStatusPageMsg") }}
|
||||
</Confirm>
|
||||
|
||||
<component is="style" v-if="config.customCSS" type="text/css">
|
||||
{{ config.customCSS }}
|
||||
</component>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -247,11 +289,20 @@ import dayjs from "dayjs";
|
||||
import Favico from "favico.js";
|
||||
import { getResBaseURL } from "../util-frontend";
|
||||
import Confirm from "../components/Confirm.vue";
|
||||
// import Prism Editor
|
||||
import { PrismEditor } from "vue-prism-editor";
|
||||
import "vue-prism-editor/dist/prismeditor.min.css"; // import the styles somewhere
|
||||
|
||||
// import highlighting library (you can use any library you want just return html string)
|
||||
import { highlight, languages } from "prismjs/components/prism-core";
|
||||
import "prismjs/components/prism-css";
|
||||
import "prismjs/themes/prism-tomorrow.css"; // import syntax highlighting styles
|
||||
|
||||
const toast = useToast();
|
||||
|
||||
const leavePageMsg = "Do you really want to leave? you have unsaved changes!";
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
let feedInterval;
|
||||
|
||||
const favicon = new Favico({
|
||||
@@ -259,10 +310,12 @@ const favicon = new Favico({
|
||||
});
|
||||
|
||||
export default {
|
||||
|
||||
components: {
|
||||
PublicGroupList,
|
||||
ImageCropUpload,
|
||||
Confirm,
|
||||
PrismEditor,
|
||||
},
|
||||
|
||||
// Leave Page for vue route change
|
||||
@@ -278,6 +331,14 @@ export default {
|
||||
next();
|
||||
},
|
||||
|
||||
props: {
|
||||
overrideSlug: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
slug: null,
|
||||
@@ -294,7 +355,6 @@ export default {
|
||||
loadedData: false,
|
||||
baseURL: "",
|
||||
clickedEditButton: false,
|
||||
domainNamesPlaceholder: "domain1.com\ndomain2.com\n..."
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -389,6 +449,29 @@ export default {
|
||||
},
|
||||
watch: {
|
||||
|
||||
/**
|
||||
* If connected to the socket and logged in, request private data of this statusPage
|
||||
* @param connected
|
||||
*/
|
||||
"$root.loggedIn"(loggedIn) {
|
||||
if (loggedIn) {
|
||||
this.$root.getSocket().emit("getStatusPage", this.slug, (res) => {
|
||||
if (res.ok) {
|
||||
this.config = res.config;
|
||||
|
||||
if (!this.config.customCSS) {
|
||||
this.config.customCSS = "body {\n" +
|
||||
" \n" +
|
||||
"}\n";
|
||||
}
|
||||
|
||||
} else {
|
||||
toast.error(res.msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Selected a monitor and add to the list.
|
||||
*/
|
||||
@@ -449,7 +532,7 @@ export default {
|
||||
this.baseURL = getResBaseURL();
|
||||
},
|
||||
async mounted() {
|
||||
this.slug = this.$route.params.slug;
|
||||
this.slug = this.overrideSlug || this.$route.params.slug;
|
||||
|
||||
if (!this.slug) {
|
||||
this.slug = "default";
|
||||
@@ -458,6 +541,10 @@ export default {
|
||||
axios.get("/api/status-page/" + this.slug).then((res) => {
|
||||
this.config = res.data.config;
|
||||
|
||||
if (!this.config.domainNameList) {
|
||||
this.config.domainNameList = [];
|
||||
}
|
||||
|
||||
if (this.config.icon) {
|
||||
this.imgDataUrl = this.config.icon;
|
||||
}
|
||||
@@ -480,6 +567,10 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
|
||||
highlighter(code) {
|
||||
return highlight(code, languages.css);
|
||||
},
|
||||
|
||||
updateHeartbeatList() {
|
||||
// If editMode, it will use the data from websocket.
|
||||
if (! this.editMode) {
|
||||
@@ -575,6 +666,10 @@ export default {
|
||||
});
|
||||
},
|
||||
|
||||
addDomainField() {
|
||||
this.config.domainNameList.push("");
|
||||
},
|
||||
|
||||
discard() {
|
||||
location.href = "/status/" + this.slug;
|
||||
},
|
||||
@@ -592,6 +687,11 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
statusPageLogoLoaded(eventPayload) {
|
||||
// Remark: may not work in dev, due to cros
|
||||
favicon.image(eventPayload.target);
|
||||
},
|
||||
|
||||
createIncident() {
|
||||
this.enableEditIncidentMode = true;
|
||||
|
||||
@@ -607,7 +707,7 @@ export default {
|
||||
},
|
||||
|
||||
postIncident() {
|
||||
if (this.incident.title == "" || this.incident.content == "") {
|
||||
if (this.incident.title === "" || this.incident.content === "") {
|
||||
toast.error(this.$t("Please input title and content"));
|
||||
return;
|
||||
}
|
||||
@@ -652,6 +752,10 @@ export default {
|
||||
return dayjs.utc(date).fromNow();
|
||||
},
|
||||
|
||||
removeDomain(index) {
|
||||
this.config.domainNameList.splice(index, 1);
|
||||
},
|
||||
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -700,9 +804,7 @@ h1 {
|
||||
top: 0;
|
||||
width: 300px;
|
||||
height: 100vh;
|
||||
padding: 15px 15px 68px 15px;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
|
||||
border-right: 1px solid #ededed;
|
||||
|
||||
.danger-zone {
|
||||
@@ -710,13 +812,25 @@ h1 {
|
||||
padding-top: 15px;
|
||||
}
|
||||
|
||||
.sidebar-body {
|
||||
padding: 0 10px 10px 10px;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
height: calc(100% - 70px);
|
||||
}
|
||||
|
||||
.sidebar-footer {
|
||||
width: 100%;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
padding: 15px;
|
||||
position: absolute;
|
||||
border-top: 1px solid #ededed;
|
||||
border-right: 1px solid #ededed;
|
||||
padding: 10px;
|
||||
width: 300px;
|
||||
height: 70px;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
background-color: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -773,7 +887,7 @@ footer {
|
||||
|
||||
.incident {
|
||||
.content {
|
||||
&[contenteditable=true] {
|
||||
&[contenteditable="true"] {
|
||||
min-height: 60px;
|
||||
}
|
||||
}
|
||||
@@ -803,9 +917,45 @@ footer {
|
||||
}
|
||||
|
||||
.sidebar-footer {
|
||||
border-right-color: $dark-border-color;
|
||||
border-top-color: $dark-border-color;
|
||||
background-color: $dark-header-bg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.domain-name-list {
|
||||
li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 10px 0 10px 10px;
|
||||
|
||||
.domain-input {
|
||||
flex-grow: 1;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
color: $dark-font-color;
|
||||
outline: none;
|
||||
|
||||
&::placeholder {
|
||||
color: #1d2634;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 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;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
Reference in New Issue
Block a user