mirror of
https://github.com/louislam/uptime-kuma.git
synced 2025-08-22 09:01:11 +08:00
Merge branch 'master' into logging
This commit is contained in:
@@ -12,6 +12,7 @@ $dark-font-color2: #020b05;
|
||||
$dark-bg: #0d1117;
|
||||
$dark-bg2: #070a10;
|
||||
$dark-border-color: #1d2634;
|
||||
$dark-header-bg: #161b22;
|
||||
|
||||
$easing-in: cubic-bezier(0.54, 0.78, 0.55, 0.97);
|
||||
$easing-out: cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
||||
|
@@ -137,7 +137,7 @@ export default {
|
||||
justify-content: space-between;
|
||||
|
||||
.dark & {
|
||||
background-color: #161b22;
|
||||
background-color: $dark-header-bg;
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
28
src/components/notifications/SerwerSMS.vue
Normal file
28
src/components/notifications/SerwerSMS.vue
Normal file
@@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<div class="mb-3">
|
||||
<label for="serwersms-username" class="form-label">{{ $t('serwersmsAPIUser') }}</label>
|
||||
<input id="serwersms-username" v-model="$parent.notification.serwersmsUsername" type="text" class="form-control" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="serwersms-key" class="form-label">{{ $t('serwersmsAPIPassword') }}</label>
|
||||
<HiddenInput id="serwersms-key" v-model="$parent.notification.serwersmsPassword" :required="true" autocomplete="one-time-code"></HiddenInput>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="serwersms-phone-number" class="form-label">{{ $t("serwersmsPhoneNumber") }}</label>
|
||||
<input id="serwersms-phone-number" v-model="$parent.notification.serwersmsPhoneNumber" type="text" class="form-control" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="serwersms-sender-name" class="form-label">{{ $t("serwersmsSenderName") }}</label>
|
||||
<input id="serwersms-sender-name" v-model="$parent.notification.serwersmsSenderName" type="text" minlength="3" maxlength="11" class="form-control">
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import HiddenInput from "../HiddenInput.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
HiddenInput,
|
||||
},
|
||||
};
|
||||
</script>
|
@@ -22,6 +22,7 @@ import Matrix from "./Matrix.vue";
|
||||
import AliyunSMS from "./AliyunSms.vue";
|
||||
import DingDing from "./DingDing.vue";
|
||||
import Bark from "./Bark.vue";
|
||||
import SerwerSMS from "./SerwerSMS.vue";
|
||||
|
||||
/**
|
||||
* Manage all notification form.
|
||||
@@ -52,7 +53,8 @@ const NotificationFormList = {
|
||||
"mattermost": Mattermost,
|
||||
"matrix": Matrix,
|
||||
"DingDing": DingDing,
|
||||
"Bark": Bark
|
||||
"Bark": Bark,
|
||||
"serwersms": SerwerSMS,
|
||||
}
|
||||
|
||||
export default NotificationFormList
|
||||
|
25
src/components/settings/About.vue
Normal file
25
src/components/settings/About.vue
Normal file
@@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<div class="d-flex justify-content-center align-items-center">
|
||||
<div class="logo d-flex flex-column justify-content-center align-items-center">
|
||||
<object class="my-4" width="200" height="200" data="/icon.svg" />
|
||||
<div class="fs-4 fw-bold">Uptime Kuma</div>
|
||||
<div>{{ $t("Version") }}: {{ $root.info.version }}</div>
|
||||
<div class="my-1 update-link"><a href="https://github.com/louislam/uptime-kuma/releases" target="_blank" rel="noopener">{{ $t("Check Update On GitHub") }}</a></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.logo {
|
||||
margin: 4em 1em;
|
||||
}
|
||||
.update-link {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
</style>
|
143
src/components/settings/Appearance.vue
Normal file
143
src/components/settings/Appearance.vue
Normal file
@@ -0,0 +1,143 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="my-4">
|
||||
<label for="language" class="form-label">
|
||||
{{ $t("Language") }}
|
||||
</label>
|
||||
<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>
|
||||
</div>
|
||||
<div class="my-4">
|
||||
<label for="timezone" class="form-label">{{ $t("Theme") }}</label>
|
||||
<div>
|
||||
<div
|
||||
class="btn-group"
|
||||
role="group"
|
||||
aria-label="Basic checkbox toggle button group"
|
||||
>
|
||||
<input
|
||||
id="btncheck1"
|
||||
v-model="$root.userTheme"
|
||||
type="radio"
|
||||
class="btn-check"
|
||||
name="theme"
|
||||
autocomplete="off"
|
||||
value="light"
|
||||
/>
|
||||
<label class="btn btn-outline-primary" for="btncheck1">
|
||||
{{ $t("Light") }}
|
||||
</label>
|
||||
|
||||
<input
|
||||
id="btncheck2"
|
||||
v-model="$root.userTheme"
|
||||
type="radio"
|
||||
class="btn-check"
|
||||
name="theme"
|
||||
autocomplete="off"
|
||||
value="dark"
|
||||
/>
|
||||
<label class="btn btn-outline-primary" for="btncheck2">
|
||||
{{ $t("Dark") }}
|
||||
</label>
|
||||
|
||||
<input
|
||||
id="btncheck3"
|
||||
v-model="$root.userTheme"
|
||||
type="radio"
|
||||
class="btn-check"
|
||||
name="theme"
|
||||
autocomplete="off"
|
||||
value="auto"
|
||||
/>
|
||||
<label class="btn btn-outline-primary" for="btncheck3">
|
||||
{{ $t("Auto") }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="my-4">
|
||||
<label class="form-label">{{ $t("Theme - Heartbeat Bar") }}</label>
|
||||
<div>
|
||||
<div
|
||||
class="btn-group"
|
||||
role="group"
|
||||
aria-label="Basic checkbox toggle button group"
|
||||
>
|
||||
<input
|
||||
id="btncheck4"
|
||||
v-model="$root.userHeartbeatBar"
|
||||
type="radio"
|
||||
class="btn-check"
|
||||
name="heartbeatBarTheme"
|
||||
autocomplete="off"
|
||||
value="normal"
|
||||
/>
|
||||
<label class="btn btn-outline-primary" for="btncheck4">
|
||||
{{ $t("Normal") }}
|
||||
</label>
|
||||
|
||||
<input
|
||||
id="btncheck5"
|
||||
v-model="$root.userHeartbeatBar"
|
||||
type="radio"
|
||||
class="btn-check"
|
||||
name="heartbeatBarTheme"
|
||||
autocomplete="off"
|
||||
value="bottom"
|
||||
/>
|
||||
<label class="btn btn-outline-primary" for="btncheck5">
|
||||
{{ $t("Bottom") }}
|
||||
</label>
|
||||
|
||||
<input
|
||||
id="btncheck6"
|
||||
v-model="$root.userHeartbeatBar"
|
||||
type="radio"
|
||||
class="btn-check"
|
||||
name="heartbeatBarTheme"
|
||||
autocomplete="off"
|
||||
value="none"
|
||||
/>
|
||||
<label class="btn btn-outline-primary" for="btncheck6">
|
||||
{{ $t("None") }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../assets/vars.scss";
|
||||
|
||||
.btn-check:active + .btn-outline-primary,
|
||||
.btn-check:checked + .btn-outline-primary,
|
||||
.btn-check:hover + .btn-outline-primary {
|
||||
color: #fff;
|
||||
|
||||
.dark & {
|
||||
color: #000;
|
||||
}
|
||||
}
|
||||
|
||||
.dark {
|
||||
.list-group-item {
|
||||
background-color: $dark-bg2;
|
||||
color: $dark-font-color;
|
||||
}
|
||||
}
|
||||
</style>
|
213
src/components/settings/Backup.vue
Normal file
213
src/components/settings/Backup.vue
Normal file
@@ -0,0 +1,213 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="my-4">
|
||||
<h4 class="mt-4 mb-2">{{ $t("Export Backup") }}</h4>
|
||||
|
||||
<p>
|
||||
{{ $t("backupDescription") }} <br />
|
||||
({{ $t("backupDescription2") }}) <br />
|
||||
</p>
|
||||
|
||||
<div class="mb-2">
|
||||
<button class="btn btn-primary" @click="downloadBackup">
|
||||
{{ $t("Export") }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
<strong>{{ $t("backupDescription3") }}</strong>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="my-4">
|
||||
<h4 class="mt-4 mb-2">{{ $t("Import Backup") }}</h4>
|
||||
|
||||
<label class="form-label">{{ $t("Options") }}:</label>
|
||||
<br />
|
||||
<div class="form-check form-check-inline">
|
||||
<input
|
||||
id="radioKeep"
|
||||
v-model="importHandle"
|
||||
class="form-check-input"
|
||||
type="radio"
|
||||
name="radioImportHandle"
|
||||
value="keep"
|
||||
/>
|
||||
<label class="form-check-label" for="radioKeep">
|
||||
{{ $t("Keep both") }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input
|
||||
id="radioSkip"
|
||||
v-model="importHandle"
|
||||
class="form-check-input"
|
||||
type="radio"
|
||||
name="radioImportHandle"
|
||||
value="skip"
|
||||
/>
|
||||
<label class="form-check-label" for="radioSkip">
|
||||
{{ $t("Skip existing") }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input
|
||||
id="radioOverwrite"
|
||||
v-model="importHandle"
|
||||
class="form-check-input"
|
||||
type="radio"
|
||||
name="radioImportHandle"
|
||||
value="overwrite"
|
||||
/>
|
||||
<label class="form-check-label" for="radioOverwrite">
|
||||
{{ $t("Overwrite") }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-text mb-2">
|
||||
{{ $t("importHandleDescription") }}
|
||||
</div>
|
||||
|
||||
<div class="mb-2">
|
||||
<input
|
||||
id="importBackup"
|
||||
type="file"
|
||||
class="form-control"
|
||||
accept="application/json"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="input-group mb-2 justify-content-end">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-primary"
|
||||
:disabled="processing"
|
||||
@click="confirmImport"
|
||||
>
|
||||
<div
|
||||
v-if="processing"
|
||||
class="spinner-border spinner-border-sm me-1"
|
||||
></div>
|
||||
{{ $t("Import") }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="importAlert"
|
||||
class="alert alert-danger mt-3"
|
||||
style="padding: 6px 16px"
|
||||
>
|
||||
{{ importAlert }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Confirm
|
||||
ref="confirmImport"
|
||||
btn-style="btn-danger"
|
||||
:yes-text="$t('Yes')"
|
||||
:no-text="$t('No')"
|
||||
@yes="importBackup"
|
||||
>
|
||||
{{ $t("confirmImportMsg") }}
|
||||
</Confirm>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Confirm from "../../components/Confirm.vue";
|
||||
import dayjs from "dayjs";
|
||||
import { useToast } from "vue-toastification";
|
||||
|
||||
const toast = useToast();
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Confirm,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
processing: false,
|
||||
importHandle: "skip",
|
||||
importAlert: null,
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
confirmImport() {
|
||||
this.$refs.confirmImport.show();
|
||||
},
|
||||
|
||||
downloadBackup() {
|
||||
let time = dayjs().format("YYYY_MM_DD-hh_mm_ss");
|
||||
let fileName = `Uptime_Kuma_Backup_${time}.json`;
|
||||
let monitorList = Object.values(this.$root.monitorList);
|
||||
let exportData = {
|
||||
version: this.$root.info.version,
|
||||
notificationList: this.$root.notificationList,
|
||||
monitorList: monitorList,
|
||||
};
|
||||
exportData = JSON.stringify(exportData, null, 4);
|
||||
let downloadItem = document.createElement("a");
|
||||
downloadItem.setAttribute(
|
||||
"href",
|
||||
"data:application/json;charset=utf-8," +
|
||||
encodeURIComponent(exportData)
|
||||
);
|
||||
downloadItem.setAttribute("download", fileName);
|
||||
downloadItem.click();
|
||||
},
|
||||
|
||||
importBackup() {
|
||||
this.processing = true;
|
||||
let uploadItem = document.getElementById("importBackup").files;
|
||||
|
||||
if (uploadItem.length <= 0) {
|
||||
this.processing = false;
|
||||
return (this.importAlert = this.$t("alertNoFile"));
|
||||
}
|
||||
|
||||
if (uploadItem.item(0).type !== "application/json") {
|
||||
this.processing = false;
|
||||
return (this.importAlert = this.$t("alertWrongFileType"));
|
||||
}
|
||||
|
||||
let fileReader = new FileReader();
|
||||
fileReader.readAsText(uploadItem.item(0));
|
||||
|
||||
fileReader.onload = (item) => {
|
||||
this.$root.uploadBackup(
|
||||
item.target.result,
|
||||
this.importHandle,
|
||||
(res) => {
|
||||
this.processing = false;
|
||||
|
||||
if (res.ok) {
|
||||
toast.success(res.msg);
|
||||
} else {
|
||||
toast.error(res.msg);
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../assets/vars.scss";
|
||||
|
||||
.dark {
|
||||
#importBackup {
|
||||
&::file-selector-button {
|
||||
color: $primary;
|
||||
background-color: $dark-bg;
|
||||
}
|
||||
|
||||
&:hover:not(:disabled):not([readonly])::file-selector-button {
|
||||
color: $dark-font-color2;
|
||||
background-color: $primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
192
src/components/settings/General.vue
Normal file
192
src/components/settings/General.vue
Normal file
@@ -0,0 +1,192 @@
|
||||
<template>
|
||||
<div>
|
||||
<form class="my-4" @submit.prevent="saveGeneral">
|
||||
<!-- Timezone -->
|
||||
<div class="mb-4">
|
||||
<label for="timezone" class="form-label">
|
||||
{{ $t("Timezone") }}
|
||||
</label>
|
||||
<select id="timezone" v-model="$root.userTimezone" class="form-select">
|
||||
<option value="auto">
|
||||
{{ $t("Auto") }}: {{ guessTimezone }}
|
||||
</option>
|
||||
<option
|
||||
v-for="(timezone, index) in timezoneList"
|
||||
:key="index"
|
||||
:value="timezone.value"
|
||||
>
|
||||
{{ timezone.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Search Engine -->
|
||||
<div class="mb-4">
|
||||
<label class="form-label">
|
||||
{{ $t("Search Engine Visibility") }}
|
||||
</label>
|
||||
|
||||
<div class="form-check">
|
||||
<input
|
||||
id="searchEngineIndexYes"
|
||||
v-model="settings.searchEngineIndex"
|
||||
class="form-check-input"
|
||||
type="radio"
|
||||
name="flexRadioDefault"
|
||||
:value="true"
|
||||
required
|
||||
/>
|
||||
<label class="form-check-label" for="searchEngineIndexYes">
|
||||
{{ $t("Allow indexing") }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input
|
||||
id="searchEngineIndexNo"
|
||||
v-model="settings.searchEngineIndex"
|
||||
class="form-check-input"
|
||||
type="radio"
|
||||
name="flexRadioDefault"
|
||||
:value="false"
|
||||
required
|
||||
/>
|
||||
<label class="form-check-label" for="searchEngineIndexNo">
|
||||
{{ $t("Discourage search engines from indexing site") }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Entry Page -->
|
||||
<div class="mb-4">
|
||||
<label class="form-label">{{ $t("Entry Page") }}</label>
|
||||
|
||||
<div class="form-check">
|
||||
<input
|
||||
id="entryPageYes"
|
||||
v-model="settings.entryPage"
|
||||
class="form-check-input"
|
||||
type="radio"
|
||||
name="statusPage"
|
||||
value="dashboard"
|
||||
required
|
||||
/>
|
||||
<label class="form-check-label" for="entryPageYes">
|
||||
{{ $t("Dashboard") }}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-check">
|
||||
<input
|
||||
id="entryPageNo"
|
||||
v-model="settings.entryPage"
|
||||
class="form-check-input"
|
||||
type="radio"
|
||||
name="statusPage"
|
||||
value="statusPage"
|
||||
required
|
||||
/>
|
||||
<label class="form-check-label" for="entryPageNo">
|
||||
{{ $t("Status Page") }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Primary Base URL -->
|
||||
<div class="mb-4">
|
||||
<label class="form-label" for="primaryBaseURL">
|
||||
{{ $t("Primary Base URL") }}
|
||||
</label>
|
||||
|
||||
<div class="input-group mb-3">
|
||||
<input
|
||||
id="primaryBaseURL"
|
||||
v-model="settings.primaryBaseURL"
|
||||
class="form-control"
|
||||
name="primaryBaseURL"
|
||||
placeholder="https://"
|
||||
pattern="https?://.+"
|
||||
/>
|
||||
<button class="btn btn-outline-primary" type="button" @click="autoGetPrimaryBaseURL">
|
||||
{{ $t("Auto Get") }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="form-text"></div>
|
||||
</div>
|
||||
|
||||
<!-- Steam API Key -->
|
||||
<div class="mb-4">
|
||||
<label class="form-label" for="steamAPIKey">
|
||||
{{ $t("Steam API Key") }}
|
||||
</label>
|
||||
<HiddenInput
|
||||
id="steamAPIKey"
|
||||
v-model="settings.steamAPIKey"
|
||||
autocomplete="one-time-code"
|
||||
/>
|
||||
<div class="form-text">
|
||||
{{ $t("steamApiKeyDescription") }}
|
||||
<a href="https://steamcommunity.com/dev" target="_blank">
|
||||
https://steamcommunity.com/dev
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Save Button -->
|
||||
<div>
|
||||
<button class="btn btn-primary" type="submit">
|
||||
{{ $t("Save") }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import HiddenInput from "../../components/HiddenInput.vue";
|
||||
import dayjs from "dayjs";
|
||||
import utc from "dayjs/plugin/utc";
|
||||
import timezone from "dayjs/plugin/timezone";
|
||||
import { timezoneList } from "../../util-frontend";
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(timezone);
|
||||
|
||||
export default {
|
||||
components: {
|
||||
HiddenInput,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
timezoneList: timezoneList(),
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
settings() {
|
||||
return this.$parent.$parent.$parent.settings;
|
||||
},
|
||||
saveSettings() {
|
||||
return this.$parent.$parent.$parent.saveSettings;
|
||||
},
|
||||
settingsLoaded() {
|
||||
return this.$parent.$parent.$parent.settingsLoaded;
|
||||
},
|
||||
guessTimezone() {
|
||||
return dayjs.tz.guess();
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
saveGeneral() {
|
||||
localStorage.timezone = this.$root.userTimezone;
|
||||
this.saveSettings();
|
||||
},
|
||||
autoGetPrimaryBaseURL() {
|
||||
this.settings.primaryBaseURL = location.protocol + "//" + location.host;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style></style>
|
133
src/components/settings/MonitorHistory.vue
Normal file
133
src/components/settings/MonitorHistory.vue
Normal file
@@ -0,0 +1,133 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="my-4">
|
||||
<label for="keepDataPeriodDays" class="form-label">
|
||||
{{
|
||||
$t("clearDataOlderThan", [
|
||||
settings.keepDataPeriodDays,
|
||||
])
|
||||
}}
|
||||
</label>
|
||||
<input
|
||||
id="keepDataPeriodDays"
|
||||
v-model="settings.keepDataPeriodDays"
|
||||
type="number"
|
||||
class="form-control"
|
||||
required
|
||||
min="1"
|
||||
step="1"
|
||||
/>
|
||||
</div>
|
||||
<div class="my-4">
|
||||
<button class="btn btn-primary" type="button" @click="saveSettings()">
|
||||
{{ $t("Save") }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="my-4">
|
||||
<div class="my-3">
|
||||
<button class="btn btn-outline-info me-2" @click="shrinkDatabase">
|
||||
{{ $t("Shrink Database") }} ({{ databaseSizeDisplay }})
|
||||
</button>
|
||||
<div class="form-text mt-2 mb-4 ms-2">{{ $t("shrinkDatabaseDescription") }}</div>
|
||||
</div>
|
||||
<button
|
||||
id="clearAllStats-btn"
|
||||
class="btn btn-outline-danger me-2 mb-2"
|
||||
@click="confirmClearStatistics"
|
||||
>
|
||||
{{ $t("Clear all statistics") }}
|
||||
</button>
|
||||
</div>
|
||||
<Confirm
|
||||
ref="confirmClearStatistics"
|
||||
btn-style="btn-danger"
|
||||
:yes-text="$t('Yes')"
|
||||
:no-text="$t('No')"
|
||||
@yes="clearStatistics"
|
||||
>
|
||||
{{ $t("confirmClearStatisticsMsg") }}
|
||||
</Confirm>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Confirm from "../../components/Confirm.vue";
|
||||
import { debug } from "../../util.ts";
|
||||
import { useToast } from "vue-toastification";
|
||||
|
||||
const toast = useToast();
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Confirm,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
databaseSize: 0,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
settings() {
|
||||
return this.$parent.$parent.$parent.settings;
|
||||
},
|
||||
saveSettings() {
|
||||
return this.$parent.$parent.$parent.saveSettings;
|
||||
},
|
||||
settingsLoaded() {
|
||||
return this.$parent.$parent.$parent.settingsLoaded;
|
||||
},
|
||||
databaseSizeDisplay() {
|
||||
return (
|
||||
Math.round((this.databaseSize / 1024 / 1024) * 10) / 10 + " MB"
|
||||
);
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.loadDatabaseSize();
|
||||
},
|
||||
|
||||
methods: {
|
||||
loadDatabaseSize() {
|
||||
debug("load database size");
|
||||
this.$root.getSocket().emit("getDatabaseSize", (res) => {
|
||||
if (res.ok) {
|
||||
this.databaseSize = res.size;
|
||||
debug("database size: " + res.size);
|
||||
} else {
|
||||
debug(res);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
shrinkDatabase() {
|
||||
this.$root.getSocket().emit("shrinkDatabase", (res) => {
|
||||
if (res.ok) {
|
||||
this.loadDatabaseSize();
|
||||
toast.success("Done");
|
||||
} else {
|
||||
debug(res);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
confirmClearStatistics() {
|
||||
this.$refs.confirmClearStatistics.show();
|
||||
},
|
||||
|
||||
clearStatistics() {
|
||||
this.$root.clearStatistics((res) => {
|
||||
if (res.ok) {
|
||||
this.$router.go();
|
||||
} else {
|
||||
toast.error(res.msg);
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style></style>
|
46
src/components/settings/Notifications.vue
Normal file
46
src/components/settings/Notifications.vue
Normal file
@@ -0,0 +1,46 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="notification-list my-4">
|
||||
<p v-if="$root.notificationList.length === 0">
|
||||
{{ $t("Not available, please setup.") }}
|
||||
</p>
|
||||
<p v-else>
|
||||
{{ $t("notificationDescription") }}
|
||||
</p>
|
||||
|
||||
<ul class="list-group mb-3" style="border-radius: 1rem;">
|
||||
<li v-for="(notification, index) in $root.notificationList" :key="index" class="list-group-item">
|
||||
{{ notification.name }}<br>
|
||||
<a href="#" @click="$refs.notificationDialog.show(notification.id)">{{ $t("Edit") }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<button class="btn btn-primary me-2" type="button" @click="$refs.notificationDialog.show()">
|
||||
{{ $t("Setup Notification") }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<NotificationDialog ref="notificationDialog" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import NotificationDialog from "../../components/NotificationDialog.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
NotificationDialog
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../assets/vars.scss";
|
||||
|
||||
.dark {
|
||||
.list-group-item {
|
||||
background-color: $dark-bg2;
|
||||
color: $dark-font-color;
|
||||
}
|
||||
}
|
||||
</style>
|
323
src/components/settings/Security.vue
Normal file
323
src/components/settings/Security.vue
Normal file
@@ -0,0 +1,323 @@
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="settingsLoaded" class="my-4">
|
||||
<!-- Change Password -->
|
||||
<template v-if="!settings.disableAuth">
|
||||
<p>
|
||||
{{ $t("Current User") }}: <strong>{{ username }}</strong>
|
||||
<button v-if="! settings.disableAuth" id="logout-btn" class="btn btn-danger ms-4 me-2 mb-2" @click="$root.logout">{{ $t("Logout") }}</button>
|
||||
</p>
|
||||
|
||||
<h5 class="my-4">{{ $t("Change Password") }}</h5>
|
||||
<form class="mb-3" @submit.prevent="savePassword">
|
||||
<div class="mb-3">
|
||||
<label for="current-password" class="form-label">
|
||||
{{ $t("Current Password") }}
|
||||
</label>
|
||||
<input
|
||||
id="current-password"
|
||||
v-model="password.currentPassword"
|
||||
type="password"
|
||||
class="form-control"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="new-password" class="form-label">
|
||||
{{ $t("New Password") }}
|
||||
</label>
|
||||
<input
|
||||
id="new-password"
|
||||
v-model="password.newPassword"
|
||||
type="password"
|
||||
class="form-control"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="repeat-new-password" class="form-label">
|
||||
{{ $t("Repeat New Password") }}
|
||||
</label>
|
||||
<input
|
||||
id="repeat-new-password"
|
||||
v-model="password.repeatNewPassword"
|
||||
type="password"
|
||||
class="form-control"
|
||||
:class="{ 'is-invalid': invalidPassword }"
|
||||
required
|
||||
/>
|
||||
<div class="invalid-feedback">
|
||||
{{ $t("passwordNotMatchMsg") }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button class="btn btn-primary" type="submit">
|
||||
{{ $t("Update Password") }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<div v-if="! settings.disableAuth" class="mt-5 mb-3">
|
||||
<h5 class="my-4">
|
||||
{{ $t("Two Factor Authentication") }}
|
||||
</h5>
|
||||
<div class="mb-4">
|
||||
<button
|
||||
class="btn btn-primary me-2"
|
||||
type="button"
|
||||
@click="$refs.TwoFADialog.show()"
|
||||
>
|
||||
{{ $t("2FA Settings") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="my-4">
|
||||
<!-- Advanced -->
|
||||
<h5 class="my-4">{{ $t("Advanced") }}</h5>
|
||||
|
||||
<div class="mb-4">
|
||||
<button v-if="settings.disableAuth" id="enableAuth-btn" class="btn btn-outline-primary me-2 mb-2" @click="enableAuth">{{ $t("Enable Auth") }}</button>
|
||||
<button v-if="! settings.disableAuth" id="disableAuth-btn" class="btn btn-primary me-2 mb-2" @click="confirmDisableAuth">{{ $t("Disable Auth") }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<TwoFADialog ref="TwoFADialog" />
|
||||
|
||||
<Confirm ref="confirmDisableAuth" btn-style="btn-danger" :yes-text="$t('I understand, please disable')" :no-text="$t('Leave')" @yes="disableAuth">
|
||||
<template v-if="$i18n.locale === 'es-ES' ">
|
||||
<p>Seguro que deseas <strong>deshabilitar la autenticación</strong>?</p>
|
||||
<p>Es para <strong>quien implementa autenticación de terceros</strong> ante Uptime Kuma como por ejemplo Cloudflare Access.</p>
|
||||
<p>Por favor usar con cuidado.</p>
|
||||
</template>
|
||||
|
||||
<template v-else-if="$i18n.locale === 'pt-BR' ">
|
||||
<p>Você tem certeza que deseja <strong>desativar a autenticação</strong>?</p>
|
||||
<p>Isso é para <strong>alguém que tem autenticação de terceiros</strong> na frente do 'UpTime Kuma' como o Cloudflare Access.</p>
|
||||
<p>Por favor, utilize isso com cautela.</p>
|
||||
</template>
|
||||
|
||||
<template v-else-if="$i18n.locale === 'zh-HK' ">
|
||||
<p>你是否確認<strong>取消登入認証</strong>?</p>
|
||||
<p>這個功能是設計給已有<strong>第三方認証</strong>的用家,例如 Cloudflare Access。</p>
|
||||
<p>請小心使用。</p>
|
||||
</template>
|
||||
|
||||
<template v-else-if="$i18n.locale === 'zh-CN' ">
|
||||
<p>是否确定 <strong>取消登录验证</strong>?</p>
|
||||
<p>这是为 <strong>有第三方认证</strong> 的用户提供的功能,如 Cloudflare Access</p>
|
||||
<p>请谨慎使用!</p>
|
||||
</template>
|
||||
|
||||
<template v-else-if="$i18n.locale === 'zh-TW' ">
|
||||
<p>你是否要<strong>取消登入驗證</strong>?</p>
|
||||
<p>此功能是設計給已有<strong>第三方認證</strong>的使用者,例如 Cloudflare Access。</p>
|
||||
<p>請謹慎使用。</p>
|
||||
</template>
|
||||
|
||||
<template v-else-if="$i18n.locale === 'de-DE' ">
|
||||
<p>Bist du sicher das du die <strong>Authentifizierung deaktivieren</strong> möchtest?</p>
|
||||
<p>Es ist für <strong>jemanden der eine externe Authentifizierung</strong> vor Uptime Kuma geschaltet hat, wie z.B. Cloudflare Access.</p>
|
||||
<p>Bitte mit Vorsicht nutzen.</p>
|
||||
</template>
|
||||
|
||||
<template v-else-if="$i18n.locale === 'sr' ">
|
||||
<p>Да ли сте сигурни да желите да <strong>искључите аутентификацију</strong>?</p>
|
||||
<p>То је за <strong>оне који имају додату аутентификацију</strong> испред Uptime Kuma као на пример Cloudflare Access.</p>
|
||||
<p>Молим Вас користите ово са пажњом.</p>
|
||||
</template>
|
||||
|
||||
<template v-else-if="$i18n.locale === 'sr-latn' ">
|
||||
<p>Da li ste sigurni da želite da <strong>isključite autentifikaciju</strong>?</p>
|
||||
<p>To je za <strong>one koji imaju dodatu autentifikaciju</strong> ispred Uptime Kuma kao na primer Cloudflare Access.</p>
|
||||
<p>Molim Vas koristite ovo sa pažnjom.</p>
|
||||
</template>
|
||||
|
||||
<template v-if="$i18n.locale === 'hr-HR' ">
|
||||
<p>Jeste li sigurni da želite <strong>isključiti autentikaciju</strong>?</p>
|
||||
<p>To je za <strong>korisnike koji imaju vanjsku autentikaciju stranice</strong> ispred Uptime Kume, poput usluge Cloudflare Access.</p>
|
||||
<p>Pažljivo koristite ovu opciju.</p>
|
||||
</template>
|
||||
|
||||
<template v-else-if="$i18n.locale === 'tr-TR' ">
|
||||
<p><strong>Şifreli girişi devre dışı bırakmak istediğinizden</strong>emin misiniz?</p>
|
||||
<p>Bu, Uptime Kuma'nın önünde Cloudflare Access gibi <strong>üçüncü taraf yetkilendirmesi olan</strong> kişiler içindir.</p>
|
||||
<p>Lütfen dikkatli kullanın.</p>
|
||||
</template>
|
||||
|
||||
<template v-else-if="$i18n.locale === 'ko-KR' ">
|
||||
<p>정말로 <strong>인증 기능을 끌까요</strong>?</p>
|
||||
<p>이 기능은 <strong>Cloudflare Access와 같은 서드파티 인증</strong>을 Uptime Kuma 앞에 둔 사용자를 위한 기능이에요.</p>
|
||||
<p>신중하게 사용하세요.</p>
|
||||
</template>
|
||||
|
||||
<template v-else-if="$i18n.locale === 'pl' ">
|
||||
<p>Czy na pewno chcesz <strong>wyłączyć autoryzację</strong>?</p>
|
||||
<p>Jest przeznaczony dla <strong>kogoś, kto ma autoryzację zewnętrzną</strong> przed Uptime Kuma, taką jak Cloudflare Access.</p>
|
||||
<p>Proszę używać ostrożnie.</p>
|
||||
</template>
|
||||
|
||||
<template v-else-if="$i18n.locale === 'et-EE' ">
|
||||
<p>Kas soovid <strong>lülitada autentimise välja</strong>?</p>
|
||||
<p>Kastuamiseks <strong>välise autentimispakkujaga</strong>, näiteks Cloudflare Access.</p>
|
||||
<p>Palun kasuta vastutustundlikult.</p>
|
||||
</template>
|
||||
|
||||
<template v-else-if="$i18n.locale === 'it-IT' ">
|
||||
<p>Si è certi di voler <strong>disabilitare l'autenticazione</strong>?</p>
|
||||
<p>È per <strong>chi ha l'autenticazione gestita da terze parti</strong> messa davanti ad Uptime Kuma, ad esempio Cloudflare Access.</p>
|
||||
<p>Utilizzare con attenzione.</p>
|
||||
</template>
|
||||
|
||||
<template v-else-if="$i18n.locale === 'id-ID' ">
|
||||
<p>Apakah Anda yakin ingin <strong>menonaktifkan autentikasi</strong>?</p>
|
||||
<p>Ini untuk <strong>mereka yang memiliki autentikasi pihak ketiga</strong> diletakkan di depan Uptime Kuma, misalnya akses Cloudflare.</p>
|
||||
<p>Gunakan dengan hati-hati.</p>
|
||||
</template>
|
||||
|
||||
<template v-else-if="$i18n.locale === 'ru-RU' ">
|
||||
<p>Вы уверены, что хотите <strong>отключить авторизацию</strong>?</p>
|
||||
<p>Это подходит для <strong>тех, у кого стоит другая авторизация</strong> перед открытием Uptime Kuma, например Cloudflare Access.</p>
|
||||
<p>Пожалуйста, используйте с осторожностью.</p>
|
||||
</template>
|
||||
|
||||
<template v-else-if="$i18n.locale === 'fa' ">
|
||||
<p>آیا مطمئن هستید که میخواهید <strong>احراز هویت را غیر فعال کنید</strong>?</p>
|
||||
<p>این ویژگی برای کسانی است که <strong> لایه امنیتی شخص ثالث دیگر بر روی این آدرس فعال کردهاند</strong>، مانند Cloudflare Access.</p>
|
||||
<p>لطفا از این امکان با دقت استفاده کنید.</p>
|
||||
</template>
|
||||
|
||||
<template v-else-if="$i18n.locale === 'bg-BG' ">
|
||||
<p>Сигурни ли сте, че желаете да <strong>изключите удостоверяването</strong>?</p>
|
||||
<p>Използва се в случаите, когато <strong>има настроен алтернативен метод за удостоверяване</strong> преди Uptime Kuma, например Cloudflare Access.</p>
|
||||
<p>Моля, използвайте с повишено внимание.</p>
|
||||
</template>
|
||||
|
||||
<template v-else-if="$i18n.locale === 'hu' ">
|
||||
<p>Biztos benne, hogy <strong>kikapcsolja a hitelesítést</strong>?</p>
|
||||
<p>Akkor érdemes, ha <strong>van 3rd-party hitelesítés</strong> az Uptime Kuma-t megelőzően mint a Cloudflare Access.</p>
|
||||
<p>Használja megfontoltan!</p>
|
||||
</template>
|
||||
|
||||
<template v-else-if="$i18n.locale === 'nb-NO' ">
|
||||
<p>Er du sikker på at du vil <strong>deaktiver autentisering</strong>?</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>
|
||||
</template>
|
||||
|
||||
<!-- English (en) -->
|
||||
<template v-else>
|
||||
<p>Are you sure want to <strong>disable auth</strong>?</p>
|
||||
<p>It is for <strong>someone who have 3rd-party auth</strong> in front of Uptime Kuma such as Cloudflare Access.</p>
|
||||
<p>Please use it carefully.</p>
|
||||
</template>
|
||||
</Confirm>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Confirm from "../../components/Confirm.vue";
|
||||
import TwoFADialog from "../../components/TwoFADialog.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Confirm,
|
||||
TwoFADialog
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
username: "",
|
||||
invalidPassword: false,
|
||||
password: {
|
||||
currentPassword: "",
|
||||
newPassword: "",
|
||||
repeatNewPassword: "",
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
settings() {
|
||||
return this.$parent.$parent.$parent.settings;
|
||||
},
|
||||
saveSettings() {
|
||||
return this.$parent.$parent.$parent.saveSettings;
|
||||
},
|
||||
settingsLoaded() {
|
||||
return this.$parent.$parent.$parent.settingsLoaded;
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
"password.repeatNewPassword"() {
|
||||
this.invalidPassword = false;
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.loadUsername();
|
||||
},
|
||||
|
||||
methods: {
|
||||
savePassword() {
|
||||
if (this.password.newPassword !== this.password.repeatNewPassword) {
|
||||
this.invalidPassword = true;
|
||||
} else {
|
||||
this.$root
|
||||
.getSocket()
|
||||
.emit("changePassword", this.password, (res) => {
|
||||
this.$root.toastRes(res);
|
||||
if (res.ok) {
|
||||
this.password.currentPassword = "";
|
||||
this.password.newPassword = "";
|
||||
this.password.repeatNewPassword = "";
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
loadUsername() {
|
||||
const jwtPayload = this.$root.getJWTPayload();
|
||||
|
||||
if (jwtPayload) {
|
||||
this.username = jwtPayload.username;
|
||||
}
|
||||
},
|
||||
|
||||
disableAuth() {
|
||||
this.settings.disableAuth = true;
|
||||
this.saveSettings();
|
||||
},
|
||||
|
||||
enableAuth() {
|
||||
this.settings.disableAuth = false;
|
||||
this.saveSettings();
|
||||
this.$root.storage().removeItem("token");
|
||||
location.reload();
|
||||
},
|
||||
|
||||
confirmDisableAuth() {
|
||||
this.$refs.confirmDisableAuth.show();
|
||||
},
|
||||
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../assets/vars.scss";
|
||||
|
||||
h5:after {
|
||||
content: "";
|
||||
display: block;
|
||||
width: 50%;
|
||||
padding-top: 8px;
|
||||
border-bottom: 1px solid $dark-border-color;
|
||||
}
|
||||
</style>
|
91
src/i18n.js
91
src/i18n.js
@@ -1,62 +1,45 @@
|
||||
import { createI18n } from "vue-i18n/index";
|
||||
import daDK from "./languages/da-DK";
|
||||
import deDE from "./languages/de-DE";
|
||||
import en from "./languages/en";
|
||||
import esEs from "./languages/es-ES";
|
||||
import etEE from "./languages/et-EE";
|
||||
import fa from "./languages/fa";
|
||||
import frFR from "./languages/fr-FR";
|
||||
import hu from "./languages/hu";
|
||||
import hrHR from "./languages/hr-HR";
|
||||
import itIT from "./languages/it-IT";
|
||||
import idID from "./languages/id-ID";
|
||||
import ja from "./languages/ja";
|
||||
import koKR from "./languages/ko-KR";
|
||||
import nlNL from "./languages/nl-NL";
|
||||
import nbNO from "./languages/nb-NO";
|
||||
import pl from "./languages/pl";
|
||||
import ptBR from "./languages/pt-BR";
|
||||
import bgBG from "./languages/bg-BG";
|
||||
import ruRU from "./languages/ru-RU";
|
||||
import sr from "./languages/sr";
|
||||
import srLatn from "./languages/sr-latn";
|
||||
import svSE from "./languages/sv-SE";
|
||||
import trTR from "./languages/tr-TR";
|
||||
import vi from "./languages/vi";
|
||||
import zhCN from "./languages/zh-CN";
|
||||
import zhHK from "./languages/zh-HK";
|
||||
import zhTW from "./languages/zh-TW";
|
||||
|
||||
const languageList = {
|
||||
en,
|
||||
"zh-HK": zhHK,
|
||||
"bg-BG": bgBG,
|
||||
"de-DE": deDE,
|
||||
"nl-NL": nlNL,
|
||||
"nb-NO": nbNO,
|
||||
"es-ES": esEs,
|
||||
"fa": fa,
|
||||
"pt-BR": ptBR,
|
||||
"fr-FR": frFR,
|
||||
"hu": hu,
|
||||
"hr-HR": hrHR,
|
||||
"it-IT": itIT,
|
||||
"id-ID" : idID,
|
||||
"ja": ja,
|
||||
"da-DK": daDK,
|
||||
"sr": sr,
|
||||
"sr-latn": srLatn,
|
||||
"sv-SE": svSE,
|
||||
"tr-TR": trTR,
|
||||
"ko-KR": koKR,
|
||||
"ru-RU": ruRU,
|
||||
"zh-CN": zhCN,
|
||||
"pl": pl,
|
||||
"et-EE": etEE,
|
||||
"vi": vi,
|
||||
"zh-TW": zhTW
|
||||
"zh-HK": "繁體中文 (香港)",
|
||||
"bg-BG": "Български",
|
||||
"de-DE": "Deutsch (Deutschland)",
|
||||
"nl-NL": "Nederlands",
|
||||
"nb-NO": "Norsk",
|
||||
"es-ES": "Español",
|
||||
"fa": "Farsi",
|
||||
"pt-BR": "Português (Brasileiro)",
|
||||
"fr-FR": "Français (France)",
|
||||
"hu": "Magyar",
|
||||
"hr-HR": "Hrvatski",
|
||||
"it-IT": "Italiano (Italian)",
|
||||
"id-ID": "Bahasa Indonesia (Indonesian)",
|
||||
"ja": "日本語",
|
||||
"da-DK": "Danish (Danmark)",
|
||||
"sr": "Српски",
|
||||
"sr-latn": "Srpski",
|
||||
"sv-SE": "Svenska",
|
||||
"tr-TR": "Türkçe",
|
||||
"ko-KR": "한국어",
|
||||
"ru-RU": "Русский",
|
||||
"zh-CN": "简体中文",
|
||||
"pl": "Polski",
|
||||
"et-EE": "eesti",
|
||||
"vi": "Vietnamese",
|
||||
"zh-TW": "繁體中文 (台灣)"
|
||||
};
|
||||
|
||||
let messages = {
|
||||
en,
|
||||
};
|
||||
|
||||
for (let lang in languageList) {
|
||||
messages[lang] = {
|
||||
languageName: languageList[lang]
|
||||
};
|
||||
}
|
||||
|
||||
const rtlLangs = ["fa"];
|
||||
|
||||
export const currentLocale = () => localStorage.locale
|
||||
@@ -73,5 +56,5 @@ export const i18n = createI18n({
|
||||
fallbackLocale: "en",
|
||||
silentFallbackWarn: true,
|
||||
silentTranslationWarn: true,
|
||||
messages: languageList,
|
||||
messages: messages,
|
||||
});
|
||||
|
@@ -4,11 +4,8 @@
|
||||
2. Create a language file (e.g. `zh-TW.js`). The filename must be ISO language code: http://www.lingoes.net/en/translator/langcode.htm
|
||||
3. Run `npm run update-language-files`. You can also use this command to check if there are new strings to translate for your language.
|
||||
4. Your language file should be filled in. You can translate now.
|
||||
5. Translate `src/pages/Settings.vue` (search for a `Confirm` component with `rel="confirmDisableAuth"`).
|
||||
6. Import your language file in `src/i18n.js` and add it to `languageList` constant.
|
||||
5. Translate `src/components/settings/Security.vue` (search for a `Confirm` component with `rel="confirmDisableAuth"`).
|
||||
6. Add it into `languageList` constant.
|
||||
7. Make a [pull request](https://github.com/louislam/uptime-kuma/pulls) when you have done.
|
||||
|
||||
One of good examples:
|
||||
https://github.com/louislam/uptime-kuma/pull/316/files
|
||||
|
||||
If you do not have programming skills, let me know in [Issues section](https://github.com/louislam/uptime-kuma/issues). I will assist you. 😏
|
||||
|
@@ -89,7 +89,7 @@ export default {
|
||||
Timezone: "Часова зона",
|
||||
"Search Engine Visibility": "Видимост за търсачки",
|
||||
"Allow indexing": "Разреши индексиране",
|
||||
"Discourage search engines from indexing site": "Обезкуражи индексирането на сайта от търсачките",
|
||||
"Discourage search engines from indexing site": "Не позволявай на търсачките да индексират този сайт",
|
||||
"Change Password": "Промени парола",
|
||||
"Current Password": "Текуща парола",
|
||||
"New Password": "Нова парола",
|
||||
@@ -307,4 +307,5 @@ export default {
|
||||
PasswordsDoNotMatch: "Паролите не съвпадат.",
|
||||
"Current User": "Текущ потребител",
|
||||
recent: "Скорошни",
|
||||
shrinkDatabaseDescription: "Инициира \"VACUUM\" за \"SQLite\" база данни. Ако Вашата база данни е създадена след версия 1.10.0, \"AUTO_VACUUM\" функцията е активна и това действие не нужно.",
|
||||
};
|
||||
|
@@ -307,4 +307,10 @@ export default {
|
||||
steamApiKeyDescription: "For monitoring a Steam Game Server you need a Steam Web-API key. You can register your API key here: ",
|
||||
"Current User": "Current User",
|
||||
recent: "Recent",
|
||||
shrinkDatabaseDescription: "Trigger database VACUUM for SQLite. If your database is created after 1.10.0, AUTO_VACUUM is already enabled and this action is not needed.",
|
||||
serwersms: "SerwerSMS.pl",
|
||||
serwersmsAPIUser: "API Username (incl. webapi_ prefix)",
|
||||
serwersmsAPIPassword: "API Password",
|
||||
serwersmsPhoneNumber: "Phone number",
|
||||
serwersmsSenderName: "SMS Sender Name (registered via customer portal)",
|
||||
};
|
||||
|
@@ -136,7 +136,7 @@ export default {
|
||||
Heartbeats: "Controlli",
|
||||
"Auto Get": "Auto Get",
|
||||
backupDescription: "È possibile fare il backup di tutti i monitoraggi e di tutte le notifiche in un file JSON.",
|
||||
backupDescription2: "P.S.: lo storico e i dati relativi agli eventi non saranno inclusi.",
|
||||
backupDescription2: "P.S.: lo storico e i dati relativi agli eventi non saranno inclusi",
|
||||
backupDescription3: "Dati sensibili come i token di autenticazione saranno inclusi nel backup, tenere quindi in un luogo sicuro.",
|
||||
alertNoFile: "Selezionare il file da importare.",
|
||||
alertWrongFileType: "Selezionare un file JSON.",
|
||||
@@ -172,7 +172,7 @@ export default {
|
||||
"Search...": "Cerca...",
|
||||
"Avg. Ping": "Tempo di risposta al ping medio",
|
||||
"Avg. Response": "Tempo di risposta medio",
|
||||
"Entry Page": "Entry Page",
|
||||
"Entry Page": "Pagina Principale",
|
||||
statusPageNothing: "Non c'è nulla qui, aggiungere un gruppo oppure un monitoraggio.",
|
||||
"No Services": "Nessun Servizio",
|
||||
"All Systems Operational": "Tutti i sistemi sono funzionali",
|
||||
@@ -307,4 +307,5 @@ export default {
|
||||
steamApiKeyDescription: "Per monitorare un server di gioco Steam si necessita della chiave Web-API di Steam. È possibile registrare la propria chiave API qui: ",
|
||||
"Current User": "Utente corrente",
|
||||
recent: "Recenti",
|
||||
shrinkDatabaseDescription: "Lancia il comando VACUUM sul database SQLite. Se il database è stato creato dopo la versione 1.10.0, AUTO_VACUUM è già abilitato e questa azione non è necessaria.",
|
||||
};
|
||||
|
@@ -307,4 +307,9 @@ export default {
|
||||
recent: "Ostatnie",
|
||||
clicksendsms: "ClickSend SMS",
|
||||
apiCredentials: "Poświadczenia API",
|
||||
serwersms: "SerwerSMS.pl",
|
||||
serwersmsAPIUser: "Nazwa użytkownika API (z prefiksem webapi_)",
|
||||
serwersmsAPIPassword: "Hasło API",
|
||||
serwersmsPhoneNumber: "Numer telefonu",
|
||||
serwersmsSenderName: "Nazwa nadawcy (zatwierdzona w panelu klienta)",
|
||||
};
|
||||
|
@@ -29,7 +29,7 @@
|
||||
</router-link>
|
||||
</li>
|
||||
<li v-if="$root.loggedIn" class="nav-item">
|
||||
<router-link to="/settings" class="nav-link">
|
||||
<router-link to="/settings" class="nav-link" :class="{ active: $route.path.includes('settings') }">
|
||||
<font-awesome-icon icon="cog" /> {{ $t("Settings") }}
|
||||
</router-link>
|
||||
</li>
|
||||
@@ -188,8 +188,8 @@ main {
|
||||
|
||||
.dark {
|
||||
header {
|
||||
background-color: #161b22;
|
||||
border-bottom-color: #161b22 !important;
|
||||
background-color: $dark-header-bg;
|
||||
border-bottom-color: $dark-header-bg !important;
|
||||
|
||||
span {
|
||||
color: #f0f6fc;
|
||||
|
@@ -12,6 +12,7 @@ import mobile from "./mixins/mobile";
|
||||
import publicMixin from "./mixins/public";
|
||||
import socket from "./mixins/socket";
|
||||
import theme from "./mixins/theme";
|
||||
import lang from "./mixins/lang";
|
||||
import { router } from "./router";
|
||||
import { appName } from "./util.ts";
|
||||
|
||||
@@ -22,6 +23,7 @@ const app = createApp({
|
||||
mobile,
|
||||
datetime,
|
||||
publicMixin,
|
||||
lang,
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
|
33
src/mixins/lang.js
Normal file
33
src/mixins/lang.js
Normal file
@@ -0,0 +1,33 @@
|
||||
import { currentLocale } from "../i18n";
|
||||
import { setPageLocale } from "../util-frontend";
|
||||
const langModules = import.meta.glob("../languages/*.js");
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
language: currentLocale(),
|
||||
};
|
||||
},
|
||||
|
||||
async created() {
|
||||
if (this.language !== "en") {
|
||||
await this.changeLang(this.language);
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
async language(lang) {
|
||||
await this.changeLang(lang);
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
async changeLang(lang) {
|
||||
let message = (await langModules["../languages/" + lang + ".js"]()).default;
|
||||
this.$i18n.setLocaleMessage(lang, message);
|
||||
this.$i18n.locale = lang;
|
||||
localStorage.locale = lang;
|
||||
setPageLocale();
|
||||
}
|
||||
}
|
||||
};
|
@@ -194,7 +194,7 @@
|
||||
</div>
|
||||
|
||||
<div class="mt-5 mb-1">
|
||||
<button class="btn btn-primary" type="submit" :disabled="processing">{{ $t("Save") }}</button>
|
||||
<button id="monitor-submit-btn" class="btn btn-primary" type="submit" :disabled="processing">{{ $t("Save") }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -265,6 +265,19 @@
|
||||
<label for="headers" class="form-label">{{ $t("Headers") }}</label>
|
||||
<textarea id="headers" v-model="monitor.headers" class="form-control" :placeholder="headersPlaceholder"></textarea>
|
||||
</div>
|
||||
|
||||
<!-- HTTP Basic Auth -->
|
||||
<h4 class="mt-5 mb-2">{{ $t("HTTP Basic Auth") }}</h4>
|
||||
|
||||
<div class="my-3">
|
||||
<label for="basicauth" class="form-label">{{ $t("Username") }}</label>
|
||||
<input id="basicauth-user" v-model="monitor.basic_auth_user" type="text" class="form-control" :placeholder="$t('Username')">
|
||||
</div>
|
||||
|
||||
<div class="my-3">
|
||||
<label for="basicauth" class="form-label">{{ $t("Password") }}</label>
|
||||
<input id="basicauth-pass" v-model="monitor.basic_auth_pass" type="password" class="form-control" :placeholder="$t('Password')">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -1,527 +1,93 @@
|
||||
<template>
|
||||
<transition name="slide-fade" appear>
|
||||
<div>
|
||||
<h1 v-show="show" class="mb-3">
|
||||
{{ $t("Settings") }}
|
||||
</h1>
|
||||
<div>
|
||||
<h1 v-show="show" class="mb-3">
|
||||
{{ $t("Settings") }}
|
||||
</h1>
|
||||
|
||||
<div class="shadow-box">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h2 class="mb-2">{{ $t("Appearance") }}</h2>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="language" class="form-label">{{ $t("Language") }}</label>
|
||||
<select id="language" v-model="$i18n.locale" class="form-select">
|
||||
<option v-for="(lang, i) in $i18n.availableLocales" :key="`Lang${i}`" :value="lang">
|
||||
{{ $i18n.messages[lang].languageName }}
|
||||
</option>
|
||||
</select>
|
||||
<div class="shadow-box">
|
||||
<div class="row">
|
||||
<div class="settings-menu">
|
||||
<router-link
|
||||
v-for="(item, key) in subMenus"
|
||||
:key="key"
|
||||
:to="`/settings/${key}`"
|
||||
>
|
||||
<div class="menu-item">
|
||||
{{ item.title }}
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="timezone" class="form-label">{{ $t("Theme") }}</label>
|
||||
|
||||
<div>
|
||||
<div class="btn-group" role="group" aria-label="Basic checkbox toggle button group">
|
||||
<input id="btncheck1" v-model="$root.userTheme" type="radio" class="btn-check" name="theme" autocomplete="off" value="light">
|
||||
<label class="btn btn-outline-primary" for="btncheck1">{{ $t("Light") }}</label>
|
||||
|
||||
<input id="btncheck2" v-model="$root.userTheme" type="radio" class="btn-check" name="theme" autocomplete="off" value="dark">
|
||||
<label class="btn btn-outline-primary" for="btncheck2">{{ $t("Dark") }}</label>
|
||||
|
||||
<input id="btncheck3" v-model="$root.userTheme" type="radio" class="btn-check" name="theme" autocomplete="off" value="auto">
|
||||
<label class="btn btn-outline-primary" for="btncheck3">{{ $t("Auto") }}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">{{ $t("Theme - Heartbeat Bar") }}</label>
|
||||
<div>
|
||||
<div class="btn-group" role="group" aria-label="Basic checkbox toggle button group">
|
||||
<input id="btncheck4" v-model="$root.userHeartbeatBar" type="radio" class="btn-check" name="heartbeatBarTheme" autocomplete="off" value="normal">
|
||||
<label class="btn btn-outline-primary" for="btncheck4">{{ $t("Normal") }}</label>
|
||||
|
||||
<input id="btncheck5" v-model="$root.userHeartbeatBar" type="radio" class="btn-check" name="heartbeatBarTheme" autocomplete="off" value="bottom">
|
||||
<label class="btn btn-outline-primary" for="btncheck5">{{ $t("Bottom") }}</label>
|
||||
|
||||
<input id="btncheck6" v-model="$root.userHeartbeatBar" type="radio" class="btn-check" name="heartbeatBarTheme" autocomplete="off" value="none">
|
||||
<label class="btn btn-outline-primary" for="btncheck6">{{ $t("None") }}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- General Settings -->
|
||||
<h2 class="mt-5 mb-2">{{ $t("General") }}</h2>
|
||||
|
||||
<form class="mb-3" @submit.prevent="saveGeneral">
|
||||
<!-- Timezone -->
|
||||
<div class="mb-4">
|
||||
<label for="timezone" class="form-label">{{ $t("Timezone") }}</label>
|
||||
<select id="timezone" v-model="$root.userTimezone" class="form-select">
|
||||
<option value="auto">
|
||||
{{ $t("Auto") }}: {{ guessTimezone }}
|
||||
</option>
|
||||
<option v-for="(timezone, index) in timezoneList" :key="index" :value="timezone.value">
|
||||
{{ timezone.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Search Engine -->
|
||||
<div class="mb-4">
|
||||
<label class="form-label">{{ $t("Search Engine Visibility") }}</label>
|
||||
|
||||
<div class="form-check">
|
||||
<input id="searchEngineIndexYes" v-model="settings.searchEngineIndex" class="form-check-input" type="radio" name="flexRadioDefault" :value="true" required>
|
||||
<label class="form-check-label" for="searchEngineIndexYes">
|
||||
{{ $t("Allow indexing") }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input id="searchEngineIndexNo" v-model="settings.searchEngineIndex" class="form-check-input" type="radio" name="flexRadioDefault" :value="false" required>
|
||||
<label class="form-check-label" for="searchEngineIndexNo">
|
||||
{{ $t("Discourage search engines from indexing site") }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Entry Page -->
|
||||
<div class="mb-4">
|
||||
<label class="form-label">{{ $t("Entry Page") }}</label>
|
||||
|
||||
<div class="form-check">
|
||||
<input id="entryPageYes" v-model="settings.entryPage" class="form-check-input" type="radio" name="statusPage" value="dashboard" required>
|
||||
<label class="form-check-label" for="entryPageYes">
|
||||
{{ $t("Dashboard") }}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-check">
|
||||
<input id="entryPageNo" v-model="settings.entryPage" class="form-check-input" type="radio" name="statusPage" value="statusPage" required>
|
||||
<label class="form-check-label" for="entryPageNo">
|
||||
{{ $t("Status Page") }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Primary Base URL -->
|
||||
<div class="mb-4">
|
||||
<label class="form-label" for="primaryBaseURL">{{ $t("Primary Base URL") }}</label>
|
||||
|
||||
<div class="input-group mb-3">
|
||||
<input id="primaryBaseURL" v-model="settings.primaryBaseURL" class="form-control" name="primaryBaseURL" placeholder="https://" pattern="https?://.+">
|
||||
<button class="btn btn-outline-primary" type="button" @click="autoGetPrimaryBaseURL">{{ $t("Auto Get") }}</button>
|
||||
</div>
|
||||
|
||||
<div class="form-text">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Steam API Key -->
|
||||
<div class="mb-4">
|
||||
<label class="form-label" for="steamAPIKey">{{ $t("Steam API Key") }}</label>
|
||||
<HiddenInput id="steamAPIKey" v-model="settings.steamAPIKey" />
|
||||
<div class="form-text">
|
||||
{{ $t("steamApiKeyDescription") }}<a href="https://steamcommunity.com/dev" target="_blank">https://steamcommunity.com/dev</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Monitor History -->
|
||||
<div class="mb-4">
|
||||
<h4 class="mt-4">{{ $t("Monitor History") }}</h4>
|
||||
<div class="mt-2">
|
||||
<label for="keepDataPeriodDays" class="form-label">{{ $t("clearDataOlderThan", [ settings.keepDataPeriodDays ]) }}</label>
|
||||
<input id="keepDataPeriodDays" v-model="settings.keepDataPeriodDays" type="number" class="form-control" required min="1" step="1">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Save Button -->
|
||||
<div>
|
||||
<button class="btn btn-primary" type="submit">
|
||||
{{ $t("Save") }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<template v-if="loaded">
|
||||
<!-- Change Password -->
|
||||
<template v-if="! settings.disableAuth">
|
||||
<h2 class="mt-5 mb-2">{{ $t("Change Password") }}</h2>
|
||||
<p>{{ $t("Current User") }}: <strong>{{ username }}</strong></p>
|
||||
<form class="mb-3" @submit.prevent="savePassword">
|
||||
<div class="mb-3">
|
||||
<label for="current-password" class="form-label">{{ $t("Current Password") }}</label>
|
||||
<input id="current-password" v-model="password.currentPassword" type="password" class="form-control" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="new-password" class="form-label">{{ $t("New Password") }}</label>
|
||||
<input id="new-password" v-model="password.newPassword" type="password" class="form-control" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="repeat-new-password" class="form-label">{{ $t("Repeat New Password") }}</label>
|
||||
<input id="repeat-new-password" v-model="password.repeatNewPassword" type="password" class="form-control" :class="{ 'is-invalid' : invalidPassword }" required>
|
||||
<div class="invalid-feedback">
|
||||
{{ $t("passwordNotMatchMsg") }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button class="btn btn-primary" type="submit">
|
||||
{{ $t("Update Password") }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<div v-if="! settings.disableAuth" class="mt-5 mb-3">
|
||||
<h2 class="mb-2">
|
||||
{{ $t("Two Factor Authentication") }}
|
||||
</h2>
|
||||
<button class="btn btn-primary me-2" type="button" @click="$refs.TwoFADialog.show()">{{ $t("2FA Settings") }}</button>
|
||||
</div>
|
||||
|
||||
<h2 class="mt-5 mb-2">{{ $t("Export Backup") }}</h2>
|
||||
|
||||
<p>
|
||||
{{ $t("backupDescription") }} <br />
|
||||
({{ $t("backupDescription2") }}) <br />
|
||||
</p>
|
||||
|
||||
<div class="mb-2">
|
||||
<button class="btn btn-primary" @click="downloadBackup">{{ $t("Export") }}</button>
|
||||
</div>
|
||||
|
||||
<p><strong>{{ $t("backupDescription3") }}</strong></p>
|
||||
|
||||
<h2 class="mt-5 mb-2">{{ $t("Import Backup") }}</h2>
|
||||
|
||||
<label class="form-label">{{ $t("Options") }}:</label>
|
||||
<br>
|
||||
<div class="form-check form-check-inline">
|
||||
<input id="radioKeep" v-model="importHandle" class="form-check-input" type="radio" name="radioImportHandle" value="keep">
|
||||
<label class="form-check-label" for="radioKeep">{{ $t("Keep both") }}</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input id="radioSkip" v-model="importHandle" class="form-check-input" type="radio" name="radioImportHandle" value="skip">
|
||||
<label class="form-check-label" for="radioSkip">{{ $t("Skip existing") }}</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input id="radioOverwrite" v-model="importHandle" class="form-check-input" type="radio" name="radioImportHandle" value="overwrite">
|
||||
<label class="form-check-label" for="radioOverwrite">{{ $t("Overwrite") }}</label>
|
||||
</div>
|
||||
<div class="form-text mb-2">
|
||||
{{ $t("importHandleDescription") }}
|
||||
</div>
|
||||
|
||||
<div class="mb-2">
|
||||
<input id="importBackup" type="file" class="form-control" accept="application/json">
|
||||
</div>
|
||||
|
||||
<div class="input-group mb-2 justify-content-end">
|
||||
<button type="button" class="btn btn-outline-primary" :disabled="processing" @click="confirmImport">
|
||||
<div v-if="processing" class="spinner-border spinner-border-sm me-1"></div>
|
||||
{{ $t("Import") }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-if="importAlert" class="alert alert-danger mt-3" style="padding: 6px 16px;">
|
||||
{{ importAlert }}
|
||||
</div>
|
||||
|
||||
<!-- Advanced -->
|
||||
<h2 class="mt-5 mb-2">{{ $t("Advanced") }}</h2>
|
||||
|
||||
<div class="mb-3">
|
||||
<button v-if="settings.disableAuth" class="btn btn-outline-primary me-2 mb-2" @click="enableAuth">{{ $t("Enable Auth") }}</button>
|
||||
<button v-if="! settings.disableAuth" class="btn btn-primary me-2 mb-2" @click="confirmDisableAuth">{{ $t("Disable Auth") }}</button>
|
||||
<button v-if="! settings.disableAuth" class="btn btn-danger me-2 mb-2" @click="$root.logout">{{ $t("Logout") }}</button>
|
||||
<button class="btn btn-outline-danger me-2 mb-2" @click="confirmClearStatistics">{{ $t("Clear all statistics") }}</button>
|
||||
<button class="btn btn-info me-2 mb-2" @click="shrinkDatabase">{{ $t("Shrink Database") }} ({{ databaseSizeDisplay }})</button>
|
||||
</div>
|
||||
</template>
|
||||
</router-link>
|
||||
</div>
|
||||
<div class="settings-content">
|
||||
<div class="settings-content-header">
|
||||
{{ subMenus[currentPage].title }}
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div v-if="$root.isMobile" class="mt-3" />
|
||||
|
||||
<!-- Notifications -->
|
||||
<div class="notification-list ">
|
||||
<h2>{{ $t("Notifications") }}</h2>
|
||||
<p v-if="$root.notificationList.length === 0">
|
||||
{{ $t("Not available, please setup.") }}
|
||||
</p>
|
||||
<p v-else>
|
||||
{{ $t("notificationDescription") }}
|
||||
</p>
|
||||
|
||||
<ul class="list-group mb-3" style="border-radius: 1rem;">
|
||||
<li v-for="(notification, index) in $root.notificationList" :key="index" class="list-group-item">
|
||||
{{ notification.name }}<br>
|
||||
<a href="#" @click="$refs.notificationDialog.show(notification.id)">{{ $t("Edit") }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<button class="btn btn-primary me-2" type="button" @click="$refs.notificationDialog.show()">
|
||||
{{ $t("Setup Notification") }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Info -->
|
||||
<h2 class="mt-5">{{ $t("Info") }}</h2>
|
||||
|
||||
{{ $t("Version") }}: {{ $root.info.version }} <br />
|
||||
<a href="https://github.com/louislam/uptime-kuma/releases" target="_blank" rel="noopener">{{ $t("Check Update On GitHub") }}</a>
|
||||
<div class="mx-3">
|
||||
<router-view v-slot="{ Component }">
|
||||
<transition name="slide-fade" appear>
|
||||
<component :is="Component" />
|
||||
</transition>
|
||||
</router-view>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<NotificationDialog ref="notificationDialog" />
|
||||
<TwoFADialog ref="TwoFADialog" />
|
||||
|
||||
<Confirm ref="confirmDisableAuth" btn-style="btn-danger" :yes-text="$t('I understand, please disable')" :no-text="$t('Leave')" @yes="disableAuth">
|
||||
<template v-if="$i18n.locale === 'es-ES' ">
|
||||
<p>Seguro que deseas <strong>deshabilitar la autenticación</strong>?</p>
|
||||
<p>Es para <strong>quien implementa autenticación de terceros</strong> ante Uptime Kuma como por ejemplo Cloudflare Access.</p>
|
||||
<p>Por favor usar con cuidado.</p>
|
||||
</template>
|
||||
|
||||
<template v-else-if="$i18n.locale === 'pt-BR' ">
|
||||
<p>Você tem certeza que deseja <strong>desativar a autenticação</strong>?</p>
|
||||
<p>Isso é para <strong>alguém que tem autenticação de terceiros</strong> na frente do 'UpTime Kuma' como o Cloudflare Access.</p>
|
||||
<p>Por favor, utilize isso com cautela.</p>
|
||||
</template>
|
||||
|
||||
<template v-else-if="$i18n.locale === 'zh-HK' ">
|
||||
<p>你是否確認<strong>取消登入認証</strong>?</p>
|
||||
<p>這個功能是設計給已有<strong>第三方認証</strong>的用家,例如 Cloudflare Access。</p>
|
||||
<p>請小心使用。</p>
|
||||
</template>
|
||||
|
||||
<template v-else-if="$i18n.locale === 'zh-CN' ">
|
||||
<p>是否确定 <strong>取消登录验证</strong>?</p>
|
||||
<p>这是为 <strong>有第三方认证</strong> 的用户提供的功能,如 Cloudflare Access</p>
|
||||
<p>请谨慎使用!</p>
|
||||
</template>
|
||||
|
||||
<template v-else-if="$i18n.locale === 'zh-TW' ">
|
||||
<p>你是否要<strong>取消登入驗證</strong>?</p>
|
||||
<p>此功能是設計給已有<strong>第三方認證</strong>的使用者,例如 Cloudflare Access。</p>
|
||||
<p>請謹慎使用。</p>
|
||||
</template>
|
||||
|
||||
<template v-else-if="$i18n.locale === 'de-DE' ">
|
||||
<p>Bist du sicher das du die <strong>Authentifizierung deaktivieren</strong> möchtest?</p>
|
||||
<p>Es ist für <strong>jemanden der eine externe Authentifizierung</strong> vor Uptime Kuma geschaltet hat, wie z.B. Cloudflare Access.</p>
|
||||
<p>Bitte mit Vorsicht nutzen.</p>
|
||||
</template>
|
||||
|
||||
<template v-else-if="$i18n.locale === 'sr' ">
|
||||
<p>Да ли сте сигурни да желите да <strong>искључите аутентификацију</strong>?</p>
|
||||
<p>То је за <strong>оне који имају додату аутентификацију</strong> испред Uptime Kuma као на пример Cloudflare Access.</p>
|
||||
<p>Молим Вас користите ово са пажњом.</p>
|
||||
</template>
|
||||
|
||||
<template v-else-if="$i18n.locale === 'sr-latn' ">
|
||||
<p>Da li ste sigurni da želite da <strong>isključite autentifikaciju</strong>?</p>
|
||||
<p>To je za <strong>one koji imaju dodatu autentifikaciju</strong> ispred Uptime Kuma kao na primer Cloudflare Access.</p>
|
||||
<p>Molim Vas koristite ovo sa pažnjom.</p>
|
||||
</template>
|
||||
|
||||
<template v-if="$i18n.locale === 'hr-HR' ">
|
||||
<p>Jeste li sigurni da želite <strong>isključiti autentikaciju</strong>?</p>
|
||||
<p>To je za <strong>korisnike koji imaju vanjsku autentikaciju stranice</strong> ispred Uptime Kume, poput usluge Cloudflare Access.</p>
|
||||
<p>Pažljivo koristite ovu opciju.</p>
|
||||
</template>
|
||||
|
||||
<template v-else-if="$i18n.locale === 'tr-TR' ">
|
||||
<p><strong>Şifreli girişi devre dışı bırakmak istediğinizden</strong>emin misiniz?</p>
|
||||
<p>Bu, Uptime Kuma'nın önünde Cloudflare Access gibi <strong>üçüncü taraf yetkilendirmesi olan</strong> kişiler içindir.</p>
|
||||
<p>Lütfen dikkatli kullanın.</p>
|
||||
</template>
|
||||
|
||||
<template v-else-if="$i18n.locale === 'ko-KR' ">
|
||||
<p>정말로 <strong>인증 기능을 끌까요</strong>?</p>
|
||||
<p>이 기능은 <strong>Cloudflare Access와 같은 서드파티 인증</strong>을 Uptime Kuma 앞에 둔 사용자를 위한 기능이에요.</p>
|
||||
<p>신중하게 사용하세요.</p>
|
||||
</template>
|
||||
|
||||
<template v-else-if="$i18n.locale === 'pl' ">
|
||||
<p>Czy na pewno chcesz <strong>wyłączyć autoryzację</strong>?</p>
|
||||
<p>Jest przeznaczony dla <strong>kogoś, kto ma autoryzację zewnętrzną</strong> przed Uptime Kuma, taką jak Cloudflare Access.</p>
|
||||
<p>Proszę używać ostrożnie.</p>
|
||||
</template>
|
||||
|
||||
<template v-else-if="$i18n.locale === 'et-EE' ">
|
||||
<p>Kas soovid <strong>lülitada autentimise välja</strong>?</p>
|
||||
<p>Kastuamiseks <strong>välise autentimispakkujaga</strong>, näiteks Cloudflare Access.</p>
|
||||
<p>Palun kasuta vastutustundlikult.</p>
|
||||
</template>
|
||||
|
||||
<template v-else-if="$i18n.locale === 'it-IT' ">
|
||||
<p>Si è certi di voler <strong>disabilitare l'autenticazione</strong>?</p>
|
||||
<p>È per <strong>chi ha l'autenticazione gestita da terze parti</strong> messa davanti ad Uptime Kuma, ad esempio Cloudflare Access.</p>
|
||||
<p>Utilizzare con attenzione.</p>
|
||||
</template>
|
||||
|
||||
<template v-else-if="$i18n.locale === 'id-ID' ">
|
||||
<p>Apakah Anda yakin ingin <strong>menonaktifkan autentikasi</strong>?</p>
|
||||
<p>Ini untuk <strong>mereka yang memiliki autentikasi pihak ketiga</strong> diletakkan di depan Uptime Kuma, misalnya akses Cloudflare.</p>
|
||||
<p>Gunakan dengan hati-hati.</p>
|
||||
</template>
|
||||
|
||||
<template v-else-if="$i18n.locale === 'ru-RU' ">
|
||||
<p>Вы уверены, что хотите <strong>отключить авторизацию</strong>?</p>
|
||||
<p>Это подходит для <strong>тех, у кого стоит другая авторизация</strong> перед открытием Uptime Kuma, например Cloudflare Access.</p>
|
||||
<p>Пожалуйста, используйте с осторожностью.</p>
|
||||
</template>
|
||||
|
||||
<template v-else-if="$i18n.locale === 'fa' ">
|
||||
<p>آیا مطمئن هستید که میخواهید <strong>احراز هویت را غیر فعال کنید</strong>?</p>
|
||||
<p>این ویژگی برای کسانی است که <strong> لایه امنیتی شخص ثالث دیگر بر روی این آدرس فعال کردهاند</strong>، مانند Cloudflare Access.</p>
|
||||
<p>لطفا از این امکان با دقت استفاده کنید.</p>
|
||||
</template>
|
||||
|
||||
<template v-else-if="$i18n.locale === 'bg-BG' ">
|
||||
<p>Сигурни ли сте, че желаете да <strong>изключите удостоверяването</strong>?</p>
|
||||
<p>Използва се в случаите, когато <strong>има настроен алтернативен метод за удостоверяване</strong> преди Uptime Kuma, например Cloudflare Access.</p>
|
||||
<p>Моля, използвайте с повишено внимание.</p>
|
||||
</template>
|
||||
|
||||
<template v-else-if="$i18n.locale === 'hu' ">
|
||||
<p>Biztos benne, hogy <strong>kikapcsolja a hitelesítést</strong>?</p>
|
||||
<p>Akkor érdemes, ha <strong>van 3rd-party hitelesítés</strong> az Uptime Kuma-t megelőzően mint a Cloudflare Access.</p>
|
||||
<p>Használja megfontoltan!</p>
|
||||
</template>
|
||||
|
||||
<template v-else-if="$i18n.locale === 'nb-NO' ">
|
||||
<p>Er du sikker på at du vil <strong>deaktiver autentisering</strong>?</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>
|
||||
</template>
|
||||
|
||||
<!-- English (en) -->
|
||||
<template v-else>
|
||||
<p>Are you sure want to <strong>disable auth</strong>?</p>
|
||||
<p>It is for <strong>someone who have 3rd-party auth</strong> in front of Uptime Kuma such as Cloudflare Access.</p>
|
||||
<p>Please use it carefully.</p>
|
||||
</template>
|
||||
</Confirm>
|
||||
|
||||
<Confirm ref="confirmClearStatistics" btn-style="btn-danger" :yes-text="$t('Yes')" :no-text="$t('No')" @yes="clearStatistics">
|
||||
{{ $t("confirmClearStatisticsMsg") }}
|
||||
</Confirm>
|
||||
<Confirm ref="confirmImport" btn-style="btn-danger" :yes-text="$t('Yes')" :no-text="$t('No')" @yes="importBackup">
|
||||
{{ $t("confirmImportMsg") }}
|
||||
</Confirm>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import HiddenInput from "../components/HiddenInput.vue";
|
||||
import Confirm from "../components/Confirm.vue";
|
||||
import dayjs from "dayjs";
|
||||
import utc from "dayjs/plugin/utc";
|
||||
import timezone from "dayjs/plugin/timezone";
|
||||
import NotificationDialog from "../components/NotificationDialog.vue";
|
||||
import TwoFADialog from "../components/TwoFADialog.vue";
|
||||
import jwt_decode from "jwt-decode";
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(timezone);
|
||||
|
||||
import { timezoneList, setPageLocale } from "../util-frontend";
|
||||
import { useToast } from "vue-toastification";
|
||||
import { log_debug } from "../util.ts";
|
||||
|
||||
const toast = useToast();
|
||||
import { useRoute } from "vue-router";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
NotificationDialog,
|
||||
TwoFADialog,
|
||||
Confirm,
|
||||
HiddenInput,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
timezoneList: timezoneList(),
|
||||
guessTimezone: dayjs.tz.guess(),
|
||||
show: true,
|
||||
invalidPassword: false,
|
||||
password: {
|
||||
currentPassword: "",
|
||||
newPassword: "",
|
||||
repeatNewPassword: "",
|
||||
},
|
||||
settings: {
|
||||
|
||||
},
|
||||
loaded: false,
|
||||
importAlert: null,
|
||||
importHandle: "skip",
|
||||
processing: false,
|
||||
databaseSize: 0,
|
||||
settings: {},
|
||||
settingsLoaded: false,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
databaseSizeDisplay() {
|
||||
return Math.round(this.databaseSize / 1024 / 1024 * 10) / 10 + " MB";
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
"password.repeatNewPassword"() {
|
||||
this.invalidPassword = false;
|
||||
currentPage() {
|
||||
let pathEnd = useRoute().path.split("/").at(-1);
|
||||
if (pathEnd == "settings" || pathEnd == null) {
|
||||
return "general";
|
||||
}
|
||||
return pathEnd;
|
||||
},
|
||||
|
||||
"$i18n.locale"() {
|
||||
localStorage.locale = this.$i18n.locale;
|
||||
setPageLocale();
|
||||
subMenus() {
|
||||
return {
|
||||
general: {
|
||||
title: this.$t("General"),
|
||||
},
|
||||
appearance: {
|
||||
title: this.$t("Appearance"),
|
||||
},
|
||||
notifications: {
|
||||
title: this.$t("Notifications"),
|
||||
},
|
||||
"monitor-history": {
|
||||
title: this.$t("Monitor History"),
|
||||
},
|
||||
security: {
|
||||
title: this.$t("Security"),
|
||||
},
|
||||
backup: {
|
||||
title: this.$t("Backup"),
|
||||
},
|
||||
about: {
|
||||
title: this.$t("About"),
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.loadUsername();
|
||||
this.loadSettings();
|
||||
this.loadDatabaseSize();
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
saveGeneral() {
|
||||
localStorage.timezone = this.$root.userTimezone;
|
||||
this.saveSettings();
|
||||
},
|
||||
|
||||
savePassword() {
|
||||
if (this.password.newPassword !== this.password.repeatNewPassword) {
|
||||
this.invalidPassword = true;
|
||||
} else {
|
||||
this.$root.getSocket().emit("changePassword", this.password, (res) => {
|
||||
this.$root.toastRes(res);
|
||||
if (res.ok) {
|
||||
this.password.currentPassword = "";
|
||||
this.password.newPassword = "";
|
||||
this.password.repeatNewPassword = "";
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
loadUsername() {
|
||||
const jwtPayload = this.$root.getJWTPayload();
|
||||
|
||||
if (jwtPayload) {
|
||||
this.username = jwtPayload.username;
|
||||
}
|
||||
},
|
||||
|
||||
loadSettings() {
|
||||
this.$root.getSocket().emit("getSettings", (res) => {
|
||||
this.settings = res.data;
|
||||
@@ -538,7 +104,7 @@ export default {
|
||||
this.settings.keepDataPeriodDays = 180;
|
||||
}
|
||||
|
||||
this.loaded = true;
|
||||
this.settingsLoaded = true;
|
||||
});
|
||||
},
|
||||
|
||||
@@ -548,116 +114,6 @@ export default {
|
||||
this.loadSettings();
|
||||
});
|
||||
},
|
||||
|
||||
confirmDisableAuth() {
|
||||
this.$refs.confirmDisableAuth.show();
|
||||
},
|
||||
|
||||
confirmClearStatistics() {
|
||||
this.$refs.confirmClearStatistics.show();
|
||||
},
|
||||
|
||||
confirmImport() {
|
||||
this.$refs.confirmImport.show();
|
||||
},
|
||||
|
||||
disableAuth() {
|
||||
this.settings.disableAuth = true;
|
||||
this.saveSettings();
|
||||
},
|
||||
|
||||
enableAuth() {
|
||||
this.settings.disableAuth = false;
|
||||
this.saveSettings();
|
||||
this.$root.storage().removeItem("token");
|
||||
location.reload();
|
||||
},
|
||||
|
||||
downloadBackup() {
|
||||
let time = dayjs().format("YYYY_MM_DD-hh_mm_ss");
|
||||
let fileName = `Uptime_Kuma_Backup_${time}.json`;
|
||||
let monitorList = Object.values(this.$root.monitorList);
|
||||
let exportData = {
|
||||
version: this.$root.info.version,
|
||||
notificationList: this.$root.notificationList,
|
||||
monitorList: monitorList,
|
||||
};
|
||||
exportData = JSON.stringify(exportData, null, 4);
|
||||
let downloadItem = document.createElement("a");
|
||||
downloadItem.setAttribute("href", "data:application/json;charset=utf-8," + encodeURIComponent(exportData));
|
||||
downloadItem.setAttribute("download", fileName);
|
||||
downloadItem.click();
|
||||
},
|
||||
|
||||
importBackup() {
|
||||
this.processing = true;
|
||||
let uploadItem = document.getElementById("importBackup").files;
|
||||
|
||||
if (uploadItem.length <= 0) {
|
||||
this.processing = false;
|
||||
return this.importAlert = this.$t("alertNoFile");
|
||||
}
|
||||
|
||||
if (uploadItem.item(0).type !== "application/json") {
|
||||
this.processing = false;
|
||||
return this.importAlert = this.$t("alertWrongFileType");
|
||||
}
|
||||
|
||||
let fileReader = new FileReader();
|
||||
fileReader.readAsText(uploadItem.item(0));
|
||||
|
||||
fileReader.onload = item => {
|
||||
this.$root.uploadBackup(item.target.result, this.importHandle, (res) => {
|
||||
this.processing = false;
|
||||
|
||||
if (res.ok) {
|
||||
toast.success(res.msg);
|
||||
} else {
|
||||
toast.error(res.msg);
|
||||
}
|
||||
});
|
||||
};
|
||||
},
|
||||
|
||||
clearStatistics() {
|
||||
this.$root.clearStatistics((res) => {
|
||||
if (res.ok) {
|
||||
this.$router.go();
|
||||
} else {
|
||||
toast.error(res.msg);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
autoGetPrimaryBaseURL() {
|
||||
this.settings.primaryBaseURL = location.protocol + "//" + location.host;
|
||||
},
|
||||
|
||||
shrinkDatabase() {
|
||||
this.$root.getSocket().emit("shrinkDatabase", (res) => {
|
||||
if (res.ok) {
|
||||
this.loadDatabaseSize();
|
||||
toast.success("Done");
|
||||
} else {
|
||||
log_debug("settings", res);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
loadDatabaseSize() {
|
||||
log_debug("settings", "load database size");
|
||||
this.$root.getSocket().emit("getDatabaseSize", (res) => {
|
||||
|
||||
if (res.ok) {
|
||||
this.databaseSize = res.size;
|
||||
log_debug("settings", "database size: " + res.size);
|
||||
} else {
|
||||
log_debug("settings", res);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -667,37 +123,7 @@ export default {
|
||||
|
||||
.shadow-box {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.btn-check:active + .btn-outline-primary,
|
||||
.btn-check:checked + .btn-outline-primary,
|
||||
.btn-check:hover + .btn-outline-primary {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.dark {
|
||||
.list-group-item {
|
||||
background-color: $dark-bg2;
|
||||
color: $dark-font-color;
|
||||
}
|
||||
|
||||
.btn-check:active + .btn-outline-primary,
|
||||
.btn-check:checked + .btn-outline-primary,
|
||||
.btn-check:hover + .btn-outline-primary {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
#importBackup {
|
||||
&::file-selector-button {
|
||||
color: $primary;
|
||||
background-color: $dark-bg;
|
||||
}
|
||||
|
||||
&:hover:not(:disabled):not([readonly])::file-selector-button {
|
||||
color: $dark-font-color2;
|
||||
background-color: $primary;
|
||||
}
|
||||
}
|
||||
min-height: calc(100vh - 155px);
|
||||
}
|
||||
|
||||
footer {
|
||||
@@ -707,4 +133,59 @@ footer {
|
||||
padding-bottom: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.settings-menu {
|
||||
flex: 0 0 auto;
|
||||
width: 300px;
|
||||
|
||||
a {
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
border-radius: 10px;
|
||||
margin: 0.5em;
|
||||
padding: 0.7em 1em;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.menu-item:hover {
|
||||
background: $highlight-white;
|
||||
|
||||
.dark & {
|
||||
background: $dark-header-bg;
|
||||
}
|
||||
}
|
||||
|
||||
.active .menu-item {
|
||||
background: $highlight-white;
|
||||
border-left: 4px solid $primary;
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
|
||||
.dark & {
|
||||
background: $dark-header-bg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.settings-content {
|
||||
flex: 0 0 auto;
|
||||
width: calc(100% - 300px);
|
||||
|
||||
.settings-content-header {
|
||||
width: calc(100% + 20px);
|
||||
border-bottom: 1px solid #dee2e6;
|
||||
border-radius: 0 10px 0 0;
|
||||
margin-top: -20px;
|
||||
margin-right: -20px;
|
||||
padding: 12.5px 1em;
|
||||
font-size: 26px;
|
||||
|
||||
.dark & {
|
||||
background: $dark-header-bg;
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -474,7 +474,7 @@ export default {
|
||||
groupName = "Services";
|
||||
}
|
||||
|
||||
this.$root.publicGroupList.push({
|
||||
this.$root.publicGroupList.unshift({
|
||||
name: groupName,
|
||||
monitorList: [],
|
||||
});
|
||||
|
@@ -11,6 +11,14 @@ import Setup from "./pages/Setup.vue";
|
||||
const StatusPage = () => import("./pages/StatusPage.vue");
|
||||
import Entry from "./pages/Entry.vue";
|
||||
|
||||
import Appearance from "./components/settings/Appearance.vue";
|
||||
import General from "./components/settings/General.vue";
|
||||
import Notifications from "./components/settings/Notifications.vue";
|
||||
import MonitorHistory from "./components/settings/MonitorHistory.vue";
|
||||
import Security from "./components/settings/Security.vue";
|
||||
import Backup from "./components/settings/Backup.vue";
|
||||
import About from "./components/settings/About.vue";
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: "/",
|
||||
@@ -59,6 +67,37 @@ const routes = [
|
||||
{
|
||||
path: "/settings",
|
||||
component: Settings,
|
||||
children: [
|
||||
{
|
||||
path: "general",
|
||||
alias: "",
|
||||
component: General,
|
||||
},
|
||||
{
|
||||
path: "appearance",
|
||||
component: Appearance,
|
||||
},
|
||||
{
|
||||
path: "notifications",
|
||||
component: Notifications,
|
||||
},
|
||||
{
|
||||
path: "monitor-history",
|
||||
component: MonitorHistory,
|
||||
},
|
||||
{
|
||||
path: "security",
|
||||
component: Security,
|
||||
},
|
||||
{
|
||||
path: "backup",
|
||||
component: Backup,
|
||||
},
|
||||
{
|
||||
path: "about",
|
||||
component: About,
|
||||
},
|
||||
]
|
||||
},
|
||||
],
|
||||
},
|
||||
|
Reference in New Issue
Block a user