Merge branch 'master' into cloudflared
This commit is contained in:
commit
a6b52b7ba6
|
@ -12,6 +12,10 @@ const { loginRateLimiter } = require("./rate-limiter");
|
||||||
* @returns {Promise<Bean|null>}
|
* @returns {Promise<Bean|null>}
|
||||||
*/
|
*/
|
||||||
exports.login = async function (username, password) {
|
exports.login = async function (username, password) {
|
||||||
|
if (typeof username !== "string" || typeof password !== "string") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
let user = await R.findOne("user", " username = ? AND active = 1 ", [
|
let user = await R.findOne("user", " username = ? AND active = 1 ", [
|
||||||
username,
|
username,
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -477,6 +477,12 @@ class Monitor extends BeanModel {
|
||||||
stop() {
|
stop() {
|
||||||
clearTimeout(this.heartbeatInterval);
|
clearTimeout(this.heartbeatInterval);
|
||||||
this.isStop = true;
|
this.isStop = true;
|
||||||
|
|
||||||
|
this.prometheus().remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
prometheus() {
|
||||||
|
return new Prometheus(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -86,6 +86,16 @@ class Prometheus {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
remove() {
|
||||||
|
try {
|
||||||
|
monitor_cert_days_remaining.remove(this.monitorLabelValues);
|
||||||
|
monitor_cert_is_valid.remove(this.monitorLabelValues);
|
||||||
|
monitor_response_time.remove(this.monitorLabelValues);
|
||||||
|
monitor_status.remove(this.monitorLabelValues);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|
|
@ -34,6 +34,14 @@ const loginRateLimiter = new KumaRateLimiter({
|
||||||
errorMessage: "Too frequently, try again later."
|
errorMessage: "Too frequently, try again later."
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const twoFaRateLimiter = new KumaRateLimiter({
|
||||||
|
tokensPerInterval: 30,
|
||||||
|
interval: "minute",
|
||||||
|
fireImmediately: true,
|
||||||
|
errorMessage: "Too frequently, try again later."
|
||||||
|
});
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
loginRateLimiter
|
loginRateLimiter,
|
||||||
|
twoFaRateLimiter,
|
||||||
};
|
};
|
||||||
|
|
123
server/server.js
123
server/server.js
|
@ -52,7 +52,7 @@ console.log("Importing this project modules");
|
||||||
debug("Importing Monitor");
|
debug("Importing Monitor");
|
||||||
const Monitor = require("./model/monitor");
|
const Monitor = require("./model/monitor");
|
||||||
debug("Importing Settings");
|
debug("Importing Settings");
|
||||||
const { getSettings, setSettings, setting, initJWTSecret, checkLogin, startUnitTest, FBSD, errorLog } = require("./util-server");
|
const { getSettings, setSettings, setting, initJWTSecret, checkLogin, startUnitTest, FBSD, errorLog, doubleCheckPassword } = require("./util-server");
|
||||||
|
|
||||||
debug("Importing Notification");
|
debug("Importing Notification");
|
||||||
const { Notification } = require("./notification");
|
const { Notification } = require("./notification");
|
||||||
|
@ -63,7 +63,7 @@ const Database = require("./database");
|
||||||
|
|
||||||
debug("Importing Background Jobs");
|
debug("Importing Background Jobs");
|
||||||
const { initBackgroundJobs } = require("./jobs");
|
const { initBackgroundJobs } = require("./jobs");
|
||||||
const { loginRateLimiter } = require("./rate-limiter");
|
const { loginRateLimiter, twoFaRateLimiter } = require("./rate-limiter");
|
||||||
|
|
||||||
const { basicAuth } = require("./auth");
|
const { basicAuth } = require("./auth");
|
||||||
const { login } = require("./auth");
|
const { login } = require("./auth");
|
||||||
|
@ -306,6 +306,15 @@ exports.entryPage = "dashboard";
|
||||||
socket.on("login", async (data, callback) => {
|
socket.on("login", async (data, callback) => {
|
||||||
console.log("Login");
|
console.log("Login");
|
||||||
|
|
||||||
|
// Checking
|
||||||
|
if (typeof callback !== "function") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Login Rate Limit
|
// Login Rate Limit
|
||||||
if (! await loginRateLimiter.pass(callback)) {
|
if (! await loginRateLimiter.pass(callback)) {
|
||||||
return;
|
return;
|
||||||
|
@ -364,14 +373,27 @@ exports.entryPage = "dashboard";
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("logout", async (callback) => {
|
socket.on("logout", async (callback) => {
|
||||||
|
// Rate Limit
|
||||||
|
if (! await loginRateLimiter.pass(callback)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
socket.leave(socket.userID);
|
socket.leave(socket.userID);
|
||||||
socket.userID = null;
|
socket.userID = null;
|
||||||
callback();
|
|
||||||
|
if (typeof callback === "function") {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("prepare2FA", async (callback) => {
|
socket.on("prepare2FA", async (currentPassword, callback) => {
|
||||||
try {
|
try {
|
||||||
|
if (! await twoFaRateLimiter.pass(callback)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
|
await doubleCheckPassword(socket, currentPassword);
|
||||||
|
|
||||||
let user = await R.findOne("user", " id = ? AND active = 1 ", [
|
let user = await R.findOne("user", " id = ? AND active = 1 ", [
|
||||||
socket.userID,
|
socket.userID,
|
||||||
|
@ -406,14 +428,19 @@ exports.entryPage = "dashboard";
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
callback({
|
callback({
|
||||||
ok: false,
|
ok: false,
|
||||||
msg: "Error while trying to prepare 2FA.",
|
msg: error.message,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("save2FA", async (callback) => {
|
socket.on("save2FA", async (currentPassword, callback) => {
|
||||||
try {
|
try {
|
||||||
|
if (! await twoFaRateLimiter.pass(callback)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
|
await doubleCheckPassword(socket, currentPassword);
|
||||||
|
|
||||||
await R.exec("UPDATE `user` SET twofa_status = 1 WHERE id = ? ", [
|
await R.exec("UPDATE `user` SET twofa_status = 1 WHERE id = ? ", [
|
||||||
socket.userID,
|
socket.userID,
|
||||||
|
@ -426,14 +453,19 @@ exports.entryPage = "dashboard";
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
callback({
|
callback({
|
||||||
ok: false,
|
ok: false,
|
||||||
msg: "Error while trying to change 2FA.",
|
msg: error.message,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("disable2FA", async (callback) => {
|
socket.on("disable2FA", async (currentPassword, callback) => {
|
||||||
try {
|
try {
|
||||||
|
if (! await twoFaRateLimiter.pass(callback)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
|
await doubleCheckPassword(socket, currentPassword);
|
||||||
await TwoFA.disable2FA(socket.userID);
|
await TwoFA.disable2FA(socket.userID);
|
||||||
|
|
||||||
callback({
|
callback({
|
||||||
|
@ -443,36 +475,47 @@ exports.entryPage = "dashboard";
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
callback({
|
callback({
|
||||||
ok: false,
|
ok: false,
|
||||||
msg: "Error while trying to change 2FA.",
|
msg: error.message,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("verifyToken", async (token, callback) => {
|
socket.on("verifyToken", async (token, currentPassword, callback) => {
|
||||||
let user = await R.findOne("user", " id = ? AND active = 1 ", [
|
try {
|
||||||
socket.userID,
|
checkLogin(socket);
|
||||||
]);
|
await doubleCheckPassword(socket, currentPassword);
|
||||||
|
|
||||||
let verify = notp.totp.verify(token, user.twofa_secret, twofa_verification_opts);
|
let user = await R.findOne("user", " id = ? AND active = 1 ", [
|
||||||
|
socket.userID,
|
||||||
|
]);
|
||||||
|
|
||||||
if (user.twofa_last_token !== token && verify) {
|
let verify = notp.totp.verify(token, user.twofa_secret, twofa_verification_opts);
|
||||||
callback({
|
|
||||||
ok: true,
|
if (user.twofa_last_token !== token && verify) {
|
||||||
valid: true,
|
callback({
|
||||||
});
|
ok: true,
|
||||||
} else {
|
valid: true,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
callback({
|
||||||
|
ok: false,
|
||||||
|
msg: "Invalid Token.",
|
||||||
|
valid: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
callback({
|
callback({
|
||||||
ok: false,
|
ok: false,
|
||||||
msg: "Invalid Token.",
|
msg: error.message,
|
||||||
valid: false,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("twoFAStatus", async (callback) => {
|
socket.on("twoFAStatus", async (callback) => {
|
||||||
checkLogin(socket);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
checkLogin(socket);
|
||||||
|
|
||||||
let user = await R.findOne("user", " id = ? AND active = 1 ", [
|
let user = await R.findOne("user", " id = ? AND active = 1 ", [
|
||||||
socket.userID,
|
socket.userID,
|
||||||
]);
|
]);
|
||||||
|
@ -489,9 +532,10 @@ exports.entryPage = "dashboard";
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
callback({
|
callback({
|
||||||
ok: false,
|
ok: false,
|
||||||
msg: "Error while trying to get 2FA status.",
|
msg: error.message,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -580,6 +624,9 @@ exports.entryPage = "dashboard";
|
||||||
throw new Error("Permission denied.");
|
throw new Error("Permission denied.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset Prometheus labels
|
||||||
|
monitorList[monitor.id]?.prometheus()?.remove();
|
||||||
|
|
||||||
bean.name = monitor.name;
|
bean.name = monitor.name;
|
||||||
bean.type = monitor.type;
|
bean.type = monitor.type;
|
||||||
bean.url = monitor.url;
|
bean.url = monitor.url;
|
||||||
|
@ -937,21 +984,13 @@ exports.entryPage = "dashboard";
|
||||||
throw new Error("Password is too weak. It should contain alphabetic and numeric characters. It must be at least 6 characters in length.");
|
throw new Error("Password is too weak. It should contain alphabetic and numeric characters. It must be at least 6 characters in length.");
|
||||||
}
|
}
|
||||||
|
|
||||||
let user = await R.findOne("user", " id = ? AND active = 1 ", [
|
let user = await doubleCheckPassword(socket, password.currentPassword);
|
||||||
socket.userID,
|
await user.resetPassword(password.newPassword);
|
||||||
]);
|
|
||||||
|
|
||||||
if (user && passwordHash.verify(password.currentPassword, user.password)) {
|
callback({
|
||||||
|
ok: true,
|
||||||
user.resetPassword(password.newPassword);
|
msg: "Password has been updated successfully.",
|
||||||
|
});
|
||||||
callback({
|
|
||||||
ok: true,
|
|
||||||
msg: "Password has been updated successfully.",
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
throw new Error("Incorrect current password");
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
callback({
|
callback({
|
||||||
|
@ -978,10 +1017,14 @@ exports.entryPage = "dashboard";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("setSettings", async (data, callback) => {
|
socket.on("setSettings", async (data, currentPassword, callback) => {
|
||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
|
|
||||||
|
if (data.disableAuth) {
|
||||||
|
await doubleCheckPassword(socket, currentPassword);
|
||||||
|
}
|
||||||
|
|
||||||
await setSettings("general", data);
|
await setSettings("general", data);
|
||||||
exports.entryPage = data.entryPage;
|
exports.entryPage = data.entryPage;
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
const tcpp = require("tcp-ping");
|
const tcpp = require("tcp-ping");
|
||||||
const Ping = require("./ping-lite");
|
const Ping = require("./ping-lite");
|
||||||
const { R } = require("redbean-node");
|
const { R } = require("redbean-node");
|
||||||
const { debug } = require("../src/util");
|
const { debug, genSecret } = require("../src/util");
|
||||||
const passwordHash = require("./password-hash");
|
const passwordHash = require("./password-hash");
|
||||||
const dayjs = require("dayjs");
|
|
||||||
const { Resolver } = require("dns");
|
const { Resolver } = require("dns");
|
||||||
const child_process = require("child_process");
|
const child_process = require("child_process");
|
||||||
const iconv = require("iconv-lite");
|
const iconv = require("iconv-lite");
|
||||||
|
@ -32,7 +31,7 @@ exports.initJWTSecret = async () => {
|
||||||
jwtSecretBean.key = "jwtSecret";
|
jwtSecretBean.key = "jwtSecret";
|
||||||
}
|
}
|
||||||
|
|
||||||
jwtSecretBean.value = passwordHash.generate(dayjs() + "");
|
jwtSecretBean.value = passwordHash.generate(genSecret());
|
||||||
await R.store(jwtSecretBean);
|
await R.store(jwtSecretBean);
|
||||||
return jwtSecretBean;
|
return jwtSecretBean;
|
||||||
};
|
};
|
||||||
|
@ -321,6 +320,28 @@ exports.checkLogin = (socket) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For logged-in users, double-check the password
|
||||||
|
* @param socket
|
||||||
|
* @param currentPassword
|
||||||
|
* @returns {Promise<Bean>}
|
||||||
|
*/
|
||||||
|
exports.doubleCheckPassword = async (socket, currentPassword) => {
|
||||||
|
if (typeof currentPassword !== "string") {
|
||||||
|
throw new Error("Wrong data type?");
|
||||||
|
}
|
||||||
|
|
||||||
|
let user = await R.findOne("user", " id = ? AND active = 1 ", [
|
||||||
|
socket.userID,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!user || !passwordHash.verify(currentPassword, user.password)) {
|
||||||
|
throw new Error("Incorrect current password");
|
||||||
|
}
|
||||||
|
|
||||||
|
return user;
|
||||||
|
};
|
||||||
|
|
||||||
exports.startUnitTest = async () => {
|
exports.startUnitTest = async () => {
|
||||||
console.log("Starting unit test...");
|
console.log("Starting unit test...");
|
||||||
const npm = /^win/.test(process.platform) ? "npm.cmd" : "npm";
|
const npm = /^win/.test(process.platform) ? "npm.cmd" : "npm";
|
||||||
|
|
|
@ -9,7 +9,9 @@
|
||||||
<a v-if="searchText != ''" class="search-icon" @click="clearSearchText">
|
<a v-if="searchText != ''" class="search-icon" @click="clearSearchText">
|
||||||
<font-awesome-icon icon="times" />
|
<font-awesome-icon icon="times" />
|
||||||
</a>
|
</a>
|
||||||
<input v-model="searchText" class="form-control search-input" :placeholder="$t('Search...')" />
|
<form>
|
||||||
|
<input v-model="searchText" class="form-control search-input" :placeholder="$t('Search...')" autocomplete="off" />
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="monitor-list" :class="{ scrollbar: scrollbar }">
|
<div class="monitor-list" :class="{ scrollbar: scrollbar }">
|
||||||
|
|
|
@ -19,6 +19,19 @@
|
||||||
</div>
|
</div>
|
||||||
<p v-if="showURI && twoFAStatus == false" class="text-break mt-2">{{ uri }}</p>
|
<p v-if="showURI && twoFAStatus == false" class="text-break mt-2">{{ uri }}</p>
|
||||||
|
|
||||||
|
<div v-if="!(uri && twoFAStatus == false)" class="mb-3">
|
||||||
|
<label for="current-password" class="form-label">
|
||||||
|
{{ $t("Current Password") }}
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="current-password"
|
||||||
|
v-model="currentPassword"
|
||||||
|
type="password"
|
||||||
|
class="form-control"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button v-if="uri == null && twoFAStatus == false" class="btn btn-primary" type="button" @click="prepare2FA()">
|
<button v-if="uri == null && twoFAStatus == false" class="btn btn-primary" type="button" @click="prepare2FA()">
|
||||||
{{ $t("Enable 2FA") }}
|
{{ $t("Enable 2FA") }}
|
||||||
</button>
|
</button>
|
||||||
|
@ -59,11 +72,11 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Modal } from "bootstrap"
|
import { Modal } from "bootstrap";
|
||||||
import Confirm from "./Confirm.vue";
|
import Confirm from "./Confirm.vue";
|
||||||
import VueQrcode from "vue-qrcode"
|
import VueQrcode from "vue-qrcode";
|
||||||
import { useToast } from "vue-toastification"
|
import { useToast } from "vue-toastification";
|
||||||
const toast = useToast()
|
const toast = useToast();
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
@ -73,35 +86,36 @@ export default {
|
||||||
props: {},
|
props: {},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
currentPassword: "",
|
||||||
processing: false,
|
processing: false,
|
||||||
uri: null,
|
uri: null,
|
||||||
tokenValid: false,
|
tokenValid: false,
|
||||||
twoFAStatus: null,
|
twoFAStatus: null,
|
||||||
token: null,
|
token: null,
|
||||||
showURI: false,
|
showURI: false,
|
||||||
}
|
};
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.modal = new Modal(this.$refs.modal)
|
this.modal = new Modal(this.$refs.modal);
|
||||||
this.getStatus();
|
this.getStatus();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
show() {
|
show() {
|
||||||
this.modal.show()
|
this.modal.show();
|
||||||
},
|
},
|
||||||
|
|
||||||
confirmEnableTwoFA() {
|
confirmEnableTwoFA() {
|
||||||
this.$refs.confirmEnableTwoFA.show()
|
this.$refs.confirmEnableTwoFA.show();
|
||||||
},
|
},
|
||||||
|
|
||||||
confirmDisableTwoFA() {
|
confirmDisableTwoFA() {
|
||||||
this.$refs.confirmDisableTwoFA.show()
|
this.$refs.confirmDisableTwoFA.show();
|
||||||
},
|
},
|
||||||
|
|
||||||
prepare2FA() {
|
prepare2FA() {
|
||||||
this.processing = true;
|
this.processing = true;
|
||||||
|
|
||||||
this.$root.getSocket().emit("prepare2FA", (res) => {
|
this.$root.getSocket().emit("prepare2FA", this.currentPassword, (res) => {
|
||||||
this.processing = false;
|
this.processing = false;
|
||||||
|
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
|
@ -109,49 +123,51 @@ export default {
|
||||||
} else {
|
} else {
|
||||||
toast.error(res.msg);
|
toast.error(res.msg);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
save2FA() {
|
save2FA() {
|
||||||
this.processing = true;
|
this.processing = true;
|
||||||
|
|
||||||
this.$root.getSocket().emit("save2FA", (res) => {
|
this.$root.getSocket().emit("save2FA", this.currentPassword, (res) => {
|
||||||
this.processing = false;
|
this.processing = false;
|
||||||
|
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
this.$root.toastRes(res)
|
this.$root.toastRes(res);
|
||||||
this.getStatus();
|
this.getStatus();
|
||||||
|
this.currentPassword = "";
|
||||||
this.modal.hide();
|
this.modal.hide();
|
||||||
} else {
|
} else {
|
||||||
toast.error(res.msg);
|
toast.error(res.msg);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
disable2FA() {
|
disable2FA() {
|
||||||
this.processing = true;
|
this.processing = true;
|
||||||
|
|
||||||
this.$root.getSocket().emit("disable2FA", (res) => {
|
this.$root.getSocket().emit("disable2FA", this.currentPassword, (res) => {
|
||||||
this.processing = false;
|
this.processing = false;
|
||||||
|
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
this.$root.toastRes(res)
|
this.$root.toastRes(res);
|
||||||
this.getStatus();
|
this.getStatus();
|
||||||
|
this.currentPassword = "";
|
||||||
this.modal.hide();
|
this.modal.hide();
|
||||||
} else {
|
} else {
|
||||||
toast.error(res.msg);
|
toast.error(res.msg);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
verifyToken() {
|
verifyToken() {
|
||||||
this.$root.getSocket().emit("verifyToken", this.token, (res) => {
|
this.$root.getSocket().emit("verifyToken", this.token, this.currentPassword, (res) => {
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
this.tokenValid = res.valid;
|
this.tokenValid = res.valid;
|
||||||
} else {
|
} else {
|
||||||
toast.error(res.msg);
|
toast.error(res.msg);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
getStatus() {
|
getStatus() {
|
||||||
|
@ -161,10 +177,10 @@ export default {
|
||||||
} else {
|
} else {
|
||||||
toast.error(res.msg);
|
toast.error(res.msg);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
|
@ -215,14 +215,14 @@
|
||||||
<p>Dette er for <strong>de som har tredjepartsautorisering</strong> foran Uptime Kuma, for eksempel Cloudflare Access.</p>
|
<p>Dette er for <strong>de som har tredjepartsautorisering</strong> foran Uptime Kuma, for eksempel Cloudflare Access.</p>
|
||||||
<p>Vennligst vær forsiktig.</p>
|
<p>Vennligst vær forsiktig.</p>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-else-if="$i18n.locale === 'cs-CZ' ">
|
<template v-else-if="$i18n.locale === 'cs-CZ' ">
|
||||||
<p>Opravdu chcete <strong>deaktivovat autentifikaci</strong>?</p>
|
<p>Opravdu chcete <strong>deaktivovat autentifikaci</strong>?</p>
|
||||||
<p>Tato možnost je určena pro případy, kdy <strong>máte autentifikaci zajištěnou třetí stranou</strong> ještě před přístupem do Uptime Kuma, například prostřednictvím Cloudflare Access.</p>
|
<p>Tato možnost je určena pro případy, kdy <strong>máte autentifikaci zajištěnou třetí stranou</strong> ještě před přístupem do Uptime Kuma, například prostřednictvím Cloudflare Access.</p>
|
||||||
<p>Používejte ji prosím s rozmyslem.</p>
|
<p>Používejte ji prosím s rozmyslem.</p>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-else-if="$i18n.locale === 'vi-VN' ">
|
<template v-else-if="$i18n.locale === 'vi-VN' ">
|
||||||
<p>Bạn có muốn <strong>TẮT XÁC THỰC</strong> không?</p>
|
<p>Bạn có muốn <strong>TẮT XÁC THỰC</strong> không?</p>
|
||||||
<p>Điều này rất nguy hiểm<strong>BẤT KỲ AI</strong> cũng có thể truy cập và cướp quyền điều khiển.</p>
|
<p>Điều này rất nguy hiểm<strong>BẤT KỲ AI</strong> cũng có thể truy cập và cướp quyền điều khiển.</p>
|
||||||
<p>Vui lòng <strong>cẩn thận</strong>.</p>
|
<p>Vui lòng <strong>cẩn thận</strong>.</p>
|
||||||
|
@ -234,6 +234,19 @@
|
||||||
<p>It is designed for scenarios <strong>where you intend to implement third-party authentication</strong> in front of Uptime Kuma such as Cloudflare Access, Authelia or other authentication mechanisms.</p>
|
<p>It is designed for scenarios <strong>where you intend to implement third-party authentication</strong> in front of Uptime Kuma such as Cloudflare Access, Authelia or other authentication mechanisms.</p>
|
||||||
<p>Please use this option carefully!</p>
|
<p>Please use this option carefully!</p>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="current-password2" class="form-label">
|
||||||
|
{{ $t("Current Password") }}
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="current-password2"
|
||||||
|
v-model="password.currentPassword"
|
||||||
|
type="password"
|
||||||
|
class="form-control"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</Confirm>
|
</Confirm>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -310,7 +323,12 @@ export default {
|
||||||
|
|
||||||
disableAuth() {
|
disableAuth() {
|
||||||
this.settings.disableAuth = true;
|
this.settings.disableAuth = true;
|
||||||
this.saveSettings();
|
|
||||||
|
// Need current password to disable auth
|
||||||
|
// Set it to empty if done
|
||||||
|
this.saveSettings(() => {
|
||||||
|
this.password.currentPassword = "";
|
||||||
|
}, this.password.currentPassword);
|
||||||
},
|
},
|
||||||
|
|
||||||
enableAuth() {
|
enableAuth() {
|
||||||
|
|
|
@ -180,8 +180,8 @@ export default {
|
||||||
"Add a monitor": "Добавить монитор",
|
"Add a monitor": "Добавить монитор",
|
||||||
"Edit Status Page": "Редактировать",
|
"Edit Status Page": "Редактировать",
|
||||||
"Go to Dashboard": "Панель управления",
|
"Go to Dashboard": "Панель управления",
|
||||||
"Status Page": "Мониторинг",
|
"Status Page": "Страница статуса",
|
||||||
"Status Pages": "Página de Status",
|
"Status Pages": "Страницы статуса",
|
||||||
Discard: "Отмена",
|
Discard: "Отмена",
|
||||||
"Create Incident": "Создать инцидент",
|
"Create Incident": "Создать инцидент",
|
||||||
"Switch to Dark Theme": "Тёмная тема",
|
"Switch to Dark Theme": "Тёмная тема",
|
||||||
|
@ -311,28 +311,28 @@ export default {
|
||||||
"One record": "Одна запись",
|
"One record": "Одна запись",
|
||||||
steamApiKeyDescription: "Для мониторинга игрового сервера Steam вам необходим Web-API ключ Steam. Зарегистрировать его можно здесь: ",
|
steamApiKeyDescription: "Для мониторинга игрового сервера Steam вам необходим Web-API ключ Steam. Зарегистрировать его можно здесь: ",
|
||||||
"Certificate Chain": "Цепочка сертификатов",
|
"Certificate Chain": "Цепочка сертификатов",
|
||||||
"Valid": "Действительный",
|
Valid: "Действительный",
|
||||||
"Hide Tags": "Скрыть тэги",
|
"Hide Tags": "Скрыть тэги",
|
||||||
Title: "Название инцидента:",
|
Title: "Название инцидента:",
|
||||||
Content: "Содержание инцидента:",
|
Content: "Содержание инцидента:",
|
||||||
Post: "Опубликовать",
|
Post: "Опубликовать",
|
||||||
"Cancel": "Отмена",
|
Cancel: "Отмена",
|
||||||
"Created": "Создано",
|
Created: "Создано",
|
||||||
"Unpin": "Открепить",
|
Unpin: "Открепить",
|
||||||
"Show Tags": "Показать тэги",
|
"Show Tags": "Показать тэги",
|
||||||
"recent": "Сейчас",
|
recent: "Сейчас",
|
||||||
"3h": "3 часа",
|
"3h": "3 часа",
|
||||||
"6h": "6 часов",
|
"6h": "6 часов",
|
||||||
"24h": "24 часа",
|
"24h": "24 часа",
|
||||||
"1w": "1 неделя",
|
"1w": "1 неделя",
|
||||||
"No monitors available.": "Нет доступных мониторов",
|
"No monitors available.": "Нет доступных мониторов",
|
||||||
"Add one": "Добавить новый",
|
"Add one": "Добавить новый",
|
||||||
"Backup": "Резервная копия",
|
Backup: "Резервная копия",
|
||||||
"Security": "Безопасность",
|
Security: "Безопасность",
|
||||||
"Shrink Database": "Сжать Базу Данных",
|
"Shrink Database": "Сжать Базу Данных",
|
||||||
"Current User": "Текущий пользователь",
|
"Current User": "Текущий пользователь",
|
||||||
"About": "О программе",
|
About: "О программе",
|
||||||
"Description": "Описание",
|
Description: "Описание",
|
||||||
"Powered by": "Работает на основе скрипта от",
|
"Powered by": "Работает на основе скрипта от",
|
||||||
shrinkDatabaseDescription: "Включает VACUUM для базы данных SQLite. Если ваша база данных была создана на версии 1.10.0 и более, AUTO_VACUUM уже включен и это действие не требуется.",
|
shrinkDatabaseDescription: "Включает VACUUM для базы данных SQLite. Если ваша база данных была создана на версии 1.10.0 и более, AUTO_VACUUM уже включен и это действие не требуется.",
|
||||||
deleteStatusPageMsg: "Вы действительно хотите удалить эту страницу статуса сервисов?",
|
deleteStatusPageMsg: "Вы действительно хотите удалить эту страницу статуса сервисов?",
|
||||||
|
@ -343,4 +343,50 @@ export default {
|
||||||
primary: "ОСНОВНОЙ",
|
primary: "ОСНОВНОЙ",
|
||||||
light: "СВЕТЛЫЙ",
|
light: "СВЕТЛЫЙ",
|
||||||
dark: "ТЕМНЫЙ",
|
dark: "ТЕМНЫЙ",
|
||||||
|
"New Status Page": "Новая страница статуса",
|
||||||
|
"Show update if available": "Показывать доступные обновления",
|
||||||
|
"Also check beta release": "Проверять обновления для бета версий",
|
||||||
|
"Add New Status Page": "Добавить страницу статуса",
|
||||||
|
Next: "Далее",
|
||||||
|
"Accept characters: a-z 0-9 -": "Разрешены символы: a-z 0-9 -",
|
||||||
|
"Start or end with a-z 0-9 only": "Начало и окончание имени только на символы: a-z 0-9",
|
||||||
|
"No consecutive dashes --": "Запрещено использовать тире --",
|
||||||
|
"HTTP Options": "HTTP Опции",
|
||||||
|
"Basic Auth": "HTTP Авторизация",
|
||||||
|
PushByTechulus: "Push by Techulus",
|
||||||
|
clicksendsms: "ClickSend SMS",
|
||||||
|
GoogleChat: "Google Chat (только Google Workspace)",
|
||||||
|
apiCredentials: "API реквизиты",
|
||||||
|
Done: "Готово",
|
||||||
|
Info: "Инфо",
|
||||||
|
"Steam API Key": "Steam API-Ключ",
|
||||||
|
"Pick a RR-Type...": "Выберите RR-Тип...",
|
||||||
|
"Pick Accepted Status Codes...": "Выберите принятые коды состояния...",
|
||||||
|
Default: "По умолчанию",
|
||||||
|
"Please input title and content": "Пожалуйста, введите название и содержание",
|
||||||
|
"Last Updated": "Последнее Обновление",
|
||||||
|
"Untitled Group": "Группа без названия",
|
||||||
|
Services: "Сервисы",
|
||||||
|
serwersms: "SerwerSMS.pl",
|
||||||
|
serwersmsAPIUser: "API Пользователь (включая префикс webapi_)",
|
||||||
|
serwersmsAPIPassword: "API Пароль",
|
||||||
|
serwersmsPhoneNumber: "Номер телефона",
|
||||||
|
serwersmsSenderName: "SMS Имя Отправителя (регистрированный через пользовательский портал)",
|
||||||
|
stackfield: "Stackfield",
|
||||||
|
smtpDkimSettings: "DKIM Настройки",
|
||||||
|
smtpDkimDesc: "Please refer to the Nodemailer DKIM {0} for usage.",
|
||||||
|
documentation: "документация",
|
||||||
|
smtpDkimDomain: "Имя Домена",
|
||||||
|
smtpDkimKeySelector: "Ключ",
|
||||||
|
smtpDkimPrivateKey: "Приватный ключ",
|
||||||
|
smtpDkimHashAlgo: "Алгоритм хэша (опционально)",
|
||||||
|
smtpDkimheaderFieldNames: "Заголовок ключей для подписи (опционально)",
|
||||||
|
smtpDkimskipFields: "Заколовок ключей не для подписи (опционально)",
|
||||||
|
gorush: "Gorush",
|
||||||
|
alerta: "Alerta",
|
||||||
|
alertaApiEndpoint: "Конечная точка API",
|
||||||
|
alertaEnvironment: "Среда",
|
||||||
|
alertaApiKey: "Ключ API",
|
||||||
|
alertaAlertState: "Состояние алерта",
|
||||||
|
alertaRecoverState: "Состояние восстановления",
|
||||||
};
|
};
|
||||||
|
|
|
@ -131,10 +131,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.$root.toastRes(res);
|
||||||
this.loadSettings();
|
this.loadSettings();
|
||||||
|
|
||||||
|
if (callback) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue