Merge branch 'master' into 1.23.X-merge-to-2.X.X

# Conflicts:
#	docker/debian-base.dockerfile
#	package-lock.json
#	server/database.js
#	server/model/monitor.js
#	server/uptime-kuma-server.js
#	server/util-server.js
This commit is contained in:
Louis Lam
2023-11-13 21:15:51 +08:00
316 changed files with 9487 additions and 4997 deletions

View File

@@ -584,6 +584,20 @@ h5.settings-subheading::after {
border-bottom: 1px solid $dark-border-color;
}
/* required class */
.code-editor, .css-editor {
/* we dont use `language-` classes anymore so thats why we need to add background and text color manually */
border-radius: 1rem;
padding: 10px 5px;
border: 1px solid #ced4da;
.dark & {
background: $dark-bg2;
border: 1px solid $dark-border-color;
}
}
$shadow-box-padding: 20px;
@@ -609,6 +623,18 @@ $shadow-box-padding: 20px;
}
}
@media (max-width: 770px) {
.toast-container {
margin-bottom: 100px !important;
}
}
@media (max-width: 550px) {
.toast-container {
margin-bottom: 126px !important;
}
}
// Localization
@import "localization.scss";

View File

@@ -92,11 +92,9 @@
<script lang="ts">
import { Modal } from "bootstrap";
import { useToast } from "vue-toastification";
import dayjs from "dayjs";
import Datepicker from "@vuepic/vue-datepicker";
import CopyableInput from "./CopyableInput.vue";
const toast = useToast();
export default {
components: {
@@ -126,6 +124,7 @@ export default {
methods: {
/**
* Show modal
* @returns {void}
*/
show() {
this.id = null;
@@ -138,7 +137,10 @@ export default {
this.keyaddmodal.show();
},
/** Submit data to server */
/**
* Submit data to server
* @returns {Promise<void>}
*/
async submit() {
this.processing = true;
@@ -154,12 +156,15 @@ export default {
this.keymodal.show();
this.clearForm();
} else {
toast.error(res.msg);
this.$root.toastError(res.msg);
}
});
},
/** Clear Form inputs */
/**
* Clear Form inputs
* @returns {void}
*/
clearForm() {
this.key = {
name: "",

View File

@@ -279,8 +279,9 @@ export default {
methods: {
/**
* Setting monitor
* @param {number} monitorId ID of monitor
* @param {string} monitorName Name of monitor
* @param {number} monitorId ID of monitor
* @param {string} monitorName Name of monitor
* @returns {void}
*/
show(monitorId, monitorName) {
this.monitor = {

View File

@@ -65,9 +65,9 @@ export default {
methods: {
/**
* Format the subject of the certificate
* @param {Object} subject Object representing the certificates
* @param {object} subject Object representing the certificates
* subject
* @returns {string}
* @returns {string} Certificate subject
*/
formatSubject(subject) {
if (subject.O && subject.CN && subject.C) {

View File

@@ -58,18 +58,23 @@ export default {
this.modal = new Modal(this.$refs.modal);
},
methods: {
/** Show the confirm dialog */
/**
* Show the confirm dialog
* @returns {void}
*/
show() {
this.modal.show();
},
/**
* @emits string "yes" Notify the parent when Yes is pressed
* @fires string "yes" Notify the parent when Yes is pressed
* @returns {void}
*/
yes() {
this.$emit("yes");
},
/**
* @emits string "no" Notify the parent when No is pressed
* @fires string "no" Notify the parent when No is pressed
* @returns {void}
*/
no() {
this.$emit("no");

View File

@@ -90,19 +90,25 @@ export default {
},
methods: {
/** Show the input */
/**
* Show the input
* @returns {void}
*/
showInput() {
this.visibility = "text";
},
/** Hide the input */
/**
* Hide the input
* @returns {void}
*/
hideInput() {
this.visibility = "password";
},
/**
* Copy the provided text to the users clipboard
* @param {string} textToCopy
* @param {string} textToCopy Text to copy to clipboard
* @returns {Promise<void>}
*/
copyToClipboard(textToCopy) {

View File

@@ -43,10 +43,17 @@ export default {
this.modal = new Modal(this.$refs.modal);
},
methods: {
/** Show the confirm dialog */
/**
* Show the confirm dialog
* @returns {void}
*/
show() {
this.modal.show();
},
/**
* Dialog confirmed
* @returns {void}
*/
confirm() {
this.$emit("added", this.groupName);
this.modal.hide();

View File

@@ -62,8 +62,6 @@
<script lang="ts">
import { Modal } from "bootstrap";
import Confirm from "./Confirm.vue";
import { useToast } from "vue-toastification";
const toast = useToast();
export default {
components: {
@@ -91,7 +89,10 @@ export default {
},
methods: {
/** Confirm deletion of docker host */
/**
* Confirm deletion of docker host
* @returns {void}
*/
deleteConfirm() {
this.modal.hide();
this.$refs.confirmDelete.show();
@@ -99,7 +100,8 @@ export default {
/**
* Show specified docker host
* @param {number} dockerHostID
* @param {number} dockerHostID ID of host to show
* @returns {void}
*/
show(dockerHostID) {
if (dockerHostID) {
@@ -116,7 +118,7 @@ export default {
}
if (!found) {
toast.error("Docker Host not found!");
this.$root.toastError("Docker Host not found!");
}
} else {
@@ -131,7 +133,10 @@ export default {
this.modal.show();
},
/** Add docker host */
/**
* Add docker host
* @returns {void}
*/
submit() {
this.processing = true;
this.$root.getSocket().emit("addDockerHost", this.dockerHost, this.id, (res) => {
@@ -150,7 +155,10 @@ export default {
});
},
/** Test the docker host */
/**
* Test the docker host
* @returns {void}
*/
test() {
this.processing = true;
this.$root.getSocket().emit("testDockerHost", this.dockerHost, (res) => {
@@ -159,7 +167,10 @@ export default {
});
},
/** Delete this docker host */
/**
* Delete this docker host
* @returns {void}
*/
deleteDockerHost() {
this.processing = true;
this.$root.getSocket().emit("deleteDockerHost", this.id, (res) => {

View File

@@ -56,6 +56,7 @@ export default {
/**
* If heartbeatList is null, get it from $root.heartbeatList
* @returns {object} Heartbeat list
*/
beatList() {
if (this.heartbeatList === null) {
@@ -67,8 +68,7 @@ export default {
/**
* Calculates the amount of beats of padding needed to fill the length of shortBeatList.
*
* @return {number} The amount of beats of padding needed to fill the length of shortBeatList.
* @returns {number} The amount of beats of padding needed to fill the length of shortBeatList.
*/
numPadding() {
if (!this.beatList) {
@@ -148,7 +148,7 @@ export default {
/**
* Returns the style object for positioning the time element.
* @return {Object} The style object containing the CSS properties for positioning the time element.
* @returns {object} The style object containing the CSS properties for positioning the time element.
*/
timeStyle() {
return {
@@ -158,8 +158,7 @@ export default {
/**
* Calculates the time elapsed since the first valid beat.
*
* @return {string} The time elapsed in minutes or hours.
* @returns {string} The time elapsed in minutes or hours.
*/
timeSinceFirstBeat() {
const firstValidBeat = this.shortBeatList.at(this.numPadding);
@@ -173,8 +172,7 @@ export default {
/**
* Calculates the elapsed time since the last valid beat was registered.
*
* @return {string} The elapsed time in a minutes, hours or "now".
* @returns {string} The elapsed time in a minutes, hours or "now".
*/
timeSinceLastBeat() {
const lastValidBeat = this.shortBeatList.at(-1);
@@ -241,7 +239,10 @@ export default {
this.resize();
},
methods: {
/** Resize the heartbeat bar */
/**
* Resize the heartbeat bar
* @returns {void}
*/
resize() {
if (this.$refs.wrap) {
this.maxBeat = Math.floor(this.$refs.wrap.clientWidth / (this.beatWidth + this.beatMargin * 2));
@@ -251,8 +252,8 @@ export default {
/**
* Get the title of the beat.
* Used as the hover tooltip on the heartbeat bar.
* @param {Object} beat Beat to get title from
* @returns {string}
* @param {object} beat Beat to get title from
* @returns {string} Beat title
*/
getBeatTitle(beat) {
return `${this.$root.datetime(beat.time)}` + ((beat.msg) ? ` - ${beat.msg}` : "");

View File

@@ -74,11 +74,17 @@ export default {
},
methods: {
/** Show users input in plain text */
/**
* Show users input in plain text
* @returns {void}
*/
showInput() {
this.visibility = "text";
},
/** Censor users input */
/**
* Censor users input
* @returns {void}
*/
hideInput() {
this.visibility = "password";
},

View File

@@ -35,7 +35,7 @@
</button>
<div v-if="res && !res.ok" class="alert alert-danger mt-3" role="alert">
{{ res.msg }}
{{ $t(res.msg) }}
</div>
</form>
</div>
@@ -64,7 +64,10 @@ export default {
},
methods: {
/** Submit the user details and attempt to log in */
/**
* Submit the user details and attempt to log in
* @returns {void}
*/
submit() {
this.processing = true;

View File

@@ -103,6 +103,7 @@ export default {
* Improve the sticky appearance of the list by increasing its
* height as user scrolls down.
* Not used on mobile.
* @returns {object} Style for monitor list
*/
boxStyle() {
if (window.innerWidth > 550) {
@@ -119,8 +120,7 @@ export default {
/**
* Returns a sorted list of monitors based on the applied filters and search text.
*
* @return {Array} The sorted list of monitors.
* @returns {Array} The sorted list of monitors.
*/
sortedMonitorList() {
let result = Object.values(this.$root.monitorList);
@@ -221,8 +221,7 @@ export default {
/**
* Determines if any filters are active.
*
* @return {boolean} True if any filter is active, false otherwise.
* @returns {boolean} True if any filter is active, false otherwise.
*/
filtersActive() {
return this.filterState.status != null || this.filterState.active != null || this.filterState.tags != null || this.searchText !== "";
@@ -267,7 +266,10 @@ export default {
window.removeEventListener("scroll", this.onScroll);
},
methods: {
/** Handle user scroll */
/**
* Handle user scroll
* @returns {void}
*/
onScroll() {
if (window.top.scrollY <= 133) {
this.windowTop = window.top.scrollY;
@@ -283,13 +285,17 @@ export default {
monitorURL(id) {
return getMonitorRelativeURL(id);
},
/** Clear the search bar */
/**
* Clear the search bar
* @returns {void}
*/
clearSearchText() {
this.searchText = "";
},
/**
* Update the MonitorList Filter
* @param {object} newFilter Object with new filter
* @returns {void}
*/
updateFilter(newFilter) {
this.filterState = newFilter;
@@ -297,6 +303,7 @@ export default {
/**
* Deselect a monitor
* @param {number} id ID of monitor
* @returns {void}
*/
deselect(id) {
delete this.selectedMonitors[id];
@@ -304,6 +311,7 @@ export default {
/**
* Select a monitor
* @param {number} id ID of monitor
* @returns {void}
*/
select(id) {
this.selectedMonitors[id] = true;
@@ -311,33 +319,45 @@ export default {
/**
* Determine if monitor is selected
* @param {number} id ID of monitor
* @returns {bool}
* @returns {bool} Is the monitor selected?
*/
isSelected(id) {
return id in this.selectedMonitors;
},
/** Disable select mode and reset selection */
/**
* Disable select mode and reset selection
* @returns {void}
*/
cancelSelectMode() {
this.selectMode = false;
this.selectedMonitors = {};
},
/** Show dialog to confirm pause */
/**
* Show dialog to confirm pause
* @returns {void}
*/
pauseDialog() {
this.$refs.confirmPause.show();
},
/** Pause each selected monitor */
/**
* Pause each selected monitor
* @returns {void}
*/
pauseSelected() {
Object.keys(this.selectedMonitors)
.filter(id => this.$root.monitorList[id].active)
.forEach(id => this.$root.getSocket().emit("pauseMonitor", id));
.forEach(id => this.$root.getSocket().emit("pauseMonitor", id, () => {}));
this.cancelSelectMode();
},
/** Resume each selected monitor */
/**
* Resume each selected monitor
* @returns {void}
*/
resumeSelected() {
Object.keys(this.selectedMonitors)
.filter(id => !this.$root.monitorList[id].active)
.forEach(id => this.$root.getSocket().emit("resumeMonitor", id));
.forEach(id => this.$root.getSocket().emit("resumeMonitor", id, () => {}));
this.cancelSelectMode();
},

View File

@@ -141,6 +141,11 @@
</div>
</div>
</li>
<li v-if="tagsList.length === 0">
<div class="dropdown-item disabled px-3">
{{ $t('No tags found.') }}
</div>
</li>
</template>
</MonitorListFilterDropdown>
</div>

View File

@@ -189,7 +189,9 @@ export default {
},
methods: {
/**
* Changes the collapsed value of the current monitor and saves it to local storage
* Changes the collapsed value of the current monitor and saves
* it to local storage
* @returns {void}
*/
changeCollapsed() {
this.isCollapsed = !this.isCollapsed;
@@ -214,6 +216,7 @@ export default {
},
/**
* Toggle selection of monitor
* @returns {void}
*/
toggleSelection() {
if (this.isSelected(this.monitor.id)) {

View File

@@ -67,8 +67,9 @@ export default {
methods: {
/**
* Setting monitor
* @param {Object} group Data of monitor
* @param {Object} monitor Data of monitor
* @param {object} group Data of monitor
* @param {object} monitor Data of monitor
* @returns {void}
*/
show(group, monitor) {
this.monitor = {
@@ -86,6 +87,7 @@ export default {
* Toggle the value of sendUrl
* @param {number} groupIndex Index of group monitor is member of
* @param {number} index Index of monitor within group
* @returns {void}
*/
toggleLink(groupIndex, index) {
this.$root.publicGroupList[groupIndex].monitorList[index].sendUrl = !this.$root.publicGroupList[groupIndex].monitorList[index].sendUrl;
@@ -95,10 +97,10 @@ export default {
* Should a link to the monitor be shown?
* Attempts to guess if a link should be shown based upon if
* sendUrl is set and if the URL is default or not.
* @param {Object} monitor Monitor to check
* @param {boolean} [ignoreSendUrl=false] Should the presence of the sendUrl
* @param {object} monitor Monitor to check
* @param {boolean} ignoreSendUrl Should the presence of the sendUrl
* property be ignored. This will only work in edit mode.
* @returns {boolean}
* @returns {boolean} Should the link be shown?
*/
showLink(monitor, ignoreSendUrl = false) {
// We must check if there are any elements in monitorList to

View File

@@ -119,6 +119,7 @@ export default {
"GoogleChat": "Google Chat (Google Workspace)",
"gorush": "Gorush",
"gotify": "Gotify",
"GrafanaOncall": "Grafana Oncall",
"HomeAssistant": "Home Assistant",
"Kook": "Kook",
"line": "LINE Messenger",
@@ -218,7 +219,10 @@ export default {
},
methods: {
/** Show dialog to confirm deletion */
/**
* Show dialog to confirm deletion
* @returns {void}
*/
deleteConfirm() {
this.modal.hide();
this.$refs.confirmDelete.show();
@@ -227,6 +231,7 @@ export default {
/**
* Show settings for specified notification
* @param {number} notificationID ID of notification to show
* @returns {void}
*/
show(notificationID) {
if (notificationID) {
@@ -250,7 +255,10 @@ export default {
this.modal.show();
},
/** Submit the form to the server */
/**
* Submit the form to the server
* @returns {void}
*/
submit() {
this.processing = true;
this.$root.getSocket().emit("addNotification", this.notification, this.id, (res) => {
@@ -269,7 +277,10 @@ export default {
});
},
/** Test the notification endpoint */
/**
* Test the notification endpoint
* @returns {void}
*/
test() {
this.processing = true;
this.$root.getSocket().emit("testNotification", this.notification, (res) => {
@@ -278,7 +289,10 @@ export default {
});
},
/** Delete the notification endpoint */
/**
* Delete the notification endpoint
* @returns {void}
*/
deleteNotification() {
this.processing = true;
this.$root.getSocket().emit("deleteNotification", this.id, (res) => {
@@ -293,7 +307,8 @@ export default {
/**
* Get a unique default name for the notification
* @param {keyof NotificationFormList} notificationKey
* @return {string}
* Notification to retrieve
* @returns {string} Default name
*/
getUniqueDefaultName(notificationKey) {

View File

@@ -21,11 +21,8 @@ import { BarController, BarElement, Chart, Filler, LinearScale, LineController,
import "chartjs-adapter-dayjs-4";
import dayjs from "dayjs";
import { Line } from "vue-chartjs";
import { useToast } from "vue-toastification";
import { DOWN, PENDING, MAINTENANCE, log } from "../util.ts";
const toast = useToast();
Chart.register(LineController, BarController, LineElement, PointElement, TimeScale, BarElement, LinearScale, Tooltip, Filler);
export default {
@@ -231,7 +228,7 @@ export default {
this.$root.getMonitorBeats(this.monitorId, newPeriod, (res) => {
if (!res.ok) {
toast.error(res.msg);
this.$root.toastError(res.msg);
} else {
this.heartbeatList = res.data;
this.$root.storage()[`chart-period-${this.monitorId}`] = newPeriod;

View File

@@ -131,7 +131,10 @@ export default {
},
methods: {
/** Show dialog to confirm deletion */
/**
* Show dialog to confirm deletion
* @returns {void}
*/
deleteConfirm() {
this.modal.hide();
this.$refs.confirmDelete.show();
@@ -140,6 +143,7 @@ export default {
/**
* Show settings for specified proxy
* @param {number} proxyID ID of proxy to show
* @returns {void}
*/
show(proxyID) {
if (proxyID) {
@@ -169,7 +173,10 @@ export default {
this.modal.show();
},
/** Submit form data for saving */
/**
* Submit form data for saving
* @returns {void}
*/
submit() {
this.processing = true;
this.$root.getSocket().emit("addProxy", this.proxy, this.id, (res) => {
@@ -187,7 +194,10 @@ export default {
});
},
/** Delete this proxy */
/**
* Delete this proxy
* @returns {void}
*/
deleteProxy() {
this.processing = true;
this.$root.getSocket().emit("deleteProxy", this.id, (res) => {

View File

@@ -131,6 +131,7 @@ export default {
/**
* Remove the specified group
* @param {number} index Index of group to remove
* @returns {void}
*/
removeGroup(index) {
this.$root.publicGroupList.splice(index, 1);
@@ -141,6 +142,7 @@ export default {
* @param {number} groupIndex Index of group to remove monitor
* from
* @param {number} index Index of monitor to remove
* @returns {void}
*/
removeMonitor(groupIndex, index) {
this.$root.publicGroupList[groupIndex].monitorList.splice(index, 1);
@@ -150,10 +152,10 @@ export default {
* Should a link to the monitor be shown?
* Attempts to guess if a link should be shown based upon if
* sendUrl is set and if the URL is default or not.
* @param {Object} monitor Monitor to check
* @param {boolean} [ignoreSendUrl=false] Should the presence of the sendUrl
* @param {object} monitor Monitor to check
* @param {boolean} ignoreSendUrl Should the presence of the sendUrl
* property be ignored. This will only work in edit mode.
* @returns {boolean}
* @returns {boolean} Should the link be shown
*/
showLink(monitor, ignoreSendUrl = false) {
// We must check if there are any elements in monitorList to
@@ -166,8 +168,8 @@ export default {
/**
* Returns formatted certificate expiry or Bad cert message
* @param {Object} monitor Monitor to show expiry for
* @returns {string}
* @param {object} monitor Monitor to show expiry for
* @returns {string} Certificate expiry message
*/
formattedCertExpiryMessage(monitor) {
if (monitor?.element?.validCert && monitor?.element?.certExpiryDaysRemaining) {
@@ -180,9 +182,9 @@ export default {
},
/**
* Returns certificate expiry based on days remaining
* @param {Object} monitor Monitor to show expiry for
* @returns {string}
* Returns certificate expiry color based on days remaining
* @param {object} monitor Monitor to show expiry for
* @returns {string} Color for certificate expiry
*/
certExpiryColor(monitor) {
if (monitor?.element?.validCert && monitor.element.certExpiryDaysRemaining > 7) {

View File

@@ -19,12 +19,13 @@
<script>
/**
* @typedef {import('./TagsManager.vue').Tag} Tag
*/
* @typedef {import('./TagsManager.vue').Tag} Tag
*/
export default {
props: {
/** Object representing tag
/**
* Object representing tag
* @type {Tag}
*/
item: {

View File

@@ -123,9 +123,7 @@ import Confirm from "./Confirm.vue";
import Tag from "./Tag.vue";
import VueMultiselect from "vue-multiselect";
import { colorOptions } from "../util-frontend";
import { useToast } from "vue-toastification";
import { getMonitorRelativeURL } from "../util.ts";
const toast = useToast();
export default {
components: {
@@ -207,6 +205,8 @@ export default {
},
/**
* Selected a monitor and add to the list.
* @param {object} monitor Monitor to add
* @returns {void}
*/
selectedAddMonitor(monitor) {
if (monitor) {
@@ -227,6 +227,7 @@ export default {
methods: {
/**
* Show confirmation for deleting a tag
* @returns {void}
*/
deleteConfirm() {
this.$refs.confirmDelete.show();
@@ -234,6 +235,7 @@ export default {
/**
* Reset the editTag form
* @returns {void}
*/
reset() {
this.selectedColor = null;
@@ -263,7 +265,7 @@ export default {
/**
* Load tag information for display in the edit dialog
* @param {Object} tag tag object to edit
* @param {object} tag tag object to edit
* @returns {void}
*/
show(tag) {
@@ -316,7 +318,7 @@ export default {
for (let addId of this.addingMonitor) {
await this.addMonitorTagAsync(this.tag.id, addId, "").then((res) => {
if (!res.ok) {
toast.error(res.msg);
this.$root.toastError(res.msg);
editResult = false;
}
});
@@ -326,7 +328,7 @@ export default {
this.monitors.find(monitor => monitor.id === removeId)?.tags.forEach(async (monitorTag) => {
await this.deleteMonitorTagAsync(this.tag.id, removeId, monitorTag.value).then((res) => {
if (!res.ok) {
toast.error(res.msg);
this.$root.toastError(res.msg);
editResult = false;
}
});
@@ -377,7 +379,7 @@ export default {
/**
* Get monitors which has a specific tag locally
* @param {number} tagId id of the tag to filter
* @returns {Object[]} list of monitors which has a specific tag
* @returns {object[]} list of monitors which has a specific tag
*/
monitorsByTag(tagId) {
return Object.values(this.$root.monitorList).filter((monitor) => {
@@ -396,7 +398,7 @@ export default {
/**
* Add a tag asynchronously
* @param {Object} newTag Object representing new tag to add
* @param {object} newTag Object representing new tag to add
* @returns {Promise<void>}
*/
addTagAsync(newTag) {

View File

@@ -129,21 +129,20 @@
<script>
import { Modal } from "bootstrap";
import VueMultiselect from "vue-multiselect";
import { useToast } from "vue-toastification";
import { colorOptions } from "../util-frontend";
import Tag from "../components/Tag.vue";
const toast = useToast();
/**
* @typedef Tag
* @type {object}
* @property {number | undefined} id
* @property {number | undefined} monitor_id
* @property {number | undefined} tag_id
* @property {string} value
* @property {string} name
* @property {string} color
* @property {boolean | undefined} new
* @property {number | undefined} id ID of tag assignment
* @property {number | undefined} monitor_id ID of monitor tag is
* assigned to
* @property {number | undefined} tag_id ID of tag
* @property {string} value Value given to tag
* @property {string} name Name of tag
* @property {string} color Colour of tag
* @property {boolean | undefined} new Should a new tag be created?
*/
export default {
@@ -152,7 +151,8 @@ export default {
VueMultiselect,
},
props: {
/** Array of tags to be pre-selected
/**
* Array of tags to be pre-selected
* @type {Tag[]}
*/
preSelectedTags: {
@@ -244,23 +244,30 @@ export default {
this.getExistingTags();
},
methods: {
/** Show the add tag dialog */
/**
* Show the add tag dialog
* @returns {void}
*/
showAddDialog() {
this.modal.show();
},
/** Get all existing tags */
/**
* Get all existing tags
* @returns {void}
*/
getExistingTags() {
this.$root.getSocket().emit("getTags", (res) => {
if (res.ok) {
this.existingTags = res.tags;
} else {
toast.error(res.msg);
this.$root.toastError(res.msg);
}
});
},
/**
* Delete the specified tag
* @param {Object} tag Object representing tag to delete
* @param {object} item Object representing tag to delete
* @returns {void}
*/
deleteTag(item) {
if (item.new) {
@@ -273,10 +280,10 @@ export default {
},
/**
* Get colour of text inside the tag
* @param {Object} option The tag that needs to be displayed.
* @param {object} option The tag that needs to be displayed.
* Defaults to "white" unless the tag has no color, which will
* then return the body color (based on application theme)
* @returns string
* @returns {string} Text color
*/
textColor(option) {
if (option.color) {
@@ -285,7 +292,10 @@ export default {
return this.$root.theme === "light" ? "var(--bs-body-color)" : "inherit";
}
},
/** Add a draft tag */
/**
* Add a draft tag
* @returns {void}
*/
addDraftTag() {
console.log("Adding Draft Tag: ", this.newDraftTag);
if (this.newDraftTag.select != null) {
@@ -313,7 +323,10 @@ export default {
}
this.clearDraftTag();
},
/** Remove a draft tag */
/**
* Remove a draft tag
* @returns {void}
*/
clearDraftTag() {
this.newDraftTag = {
name: null,
@@ -327,7 +340,7 @@ export default {
},
/**
* Add a tag asynchronously
* @param {Object} newTag Object representing new tag to add
* @param {object} newTag Object representing new tag to add
* @returns {Promise<void>}
*/
addTagAsync(newTag) {
@@ -359,7 +372,10 @@ export default {
this.$root.getSocket().emit("deleteMonitorTag", tagId, monitorId, value, resolve);
});
},
/** Handle pressing Enter key when inside the modal */
/**
* Handle pressing Enter key when inside the modal
* @returns {void}
*/
onEnter() {
if (!this.validateDraftTag.invalid) {
this.addDraftTag();
@@ -381,7 +397,7 @@ export default {
let newTagResult;
await this.addTagAsync(newTag).then((res) => {
if (!res.ok) {
toast.error(res.msg);
this.$root.toastError(res.msg);
newTagResult = false;
}
newTagResult = res.tag;
@@ -406,7 +422,7 @@ export default {
// Assign tag to monitor
await this.addMonitorTagAsync(tagId, monitorId, newTag.value).then((res) => {
if (!res.ok) {
toast.error(res.msg);
this.$root.toastError(res.msg);
newMonitorTagResult = false;
}
newMonitorTagResult = true;
@@ -422,7 +438,7 @@ export default {
let deleteMonitorTagResult;
await this.deleteMonitorTagAsync(deleteTag.tag_id, deleteTag.monitor_id, deleteTag.value).then((res) => {
if (!res.ok) {
toast.error(res.msg);
this.$root.toastError(res.msg);
deleteMonitorTagResult = false;
}
deleteMonitorTagResult = true;

View File

@@ -76,8 +76,6 @@
import { Modal } from "bootstrap";
import Confirm from "./Confirm.vue";
import VueQrcode from "vue-qrcode";
import { useToast } from "vue-toastification";
const toast = useToast();
export default {
components: {
@@ -101,22 +99,34 @@ export default {
this.getStatus();
},
methods: {
/** Show the dialog */
/**
* Show the dialog
* @returns {void}
*/
show() {
this.modal.show();
},
/** Show dialog to confirm enabling 2FA */
/**
* Show dialog to confirm enabling 2FA
* @returns {void}
*/
confirmEnableTwoFA() {
this.$refs.confirmEnableTwoFA.show();
},
/** Show dialog to confirm disabling 2FA */
/**
* Show dialog to confirm disabling 2FA
* @returns {void}
*/
confirmDisableTwoFA() {
this.$refs.confirmDisableTwoFA.show();
},
/** Prepare 2FA configuration */
/**
* Prepare 2FA configuration
* @returns {void}
*/
prepare2FA() {
this.processing = true;
@@ -126,12 +136,15 @@ export default {
if (res.ok) {
this.uri = res.uri;
} else {
toast.error(res.msg);
this.$root.toastError(res.msg);
}
});
},
/** Save the current 2FA configuration */
/**
* Save the current 2FA configuration
* @returns {void}
*/
save2FA() {
this.processing = true;
@@ -144,12 +157,15 @@ export default {
this.currentPassword = "";
this.modal.hide();
} else {
toast.error(res.msg);
this.$root.toastError(res.msg);
}
});
},
/** Disable 2FA for this user */
/**
* Disable 2FA for this user
* @returns {void}
*/
disable2FA() {
this.processing = true;
@@ -162,29 +178,35 @@ export default {
this.currentPassword = "";
this.modal.hide();
} else {
toast.error(res.msg);
this.$root.toastError(res.msg);
}
});
},
/** Verify the token generated by the user */
/**
* Verify the token generated by the user
* @returns {void}
*/
verifyToken() {
this.$root.getSocket().emit("verifyToken", this.token, this.currentPassword, (res) => {
if (res.ok) {
this.tokenValid = res.valid;
} else {
toast.error(res.msg);
this.$root.toastError(res.msg);
}
});
},
/** Get current status of 2FA */
/**
* Get current status of 2FA
* @returns {void}
*/
getStatus() {
this.$root.getSocket().emit("twoFAStatus", (res) => {
if (res.ok) {
this.twoFAStatus = res.status;
} else {
toast.error(res.msg);
this.$root.toastError(res.msg);
}
});
},

View File

@@ -84,10 +84,12 @@ export default {
},
title() {
if (this.type === "1y") {
return `1${this.$t("-year")}`;
}
if (this.type === "720") {
return `30${this.$t("-day")}`;
}
return `24${this.$t("-hour")}`;
}
},

View File

@@ -1,4 +1,11 @@
<template>
<div class="mb-3">
<label for="Bark API Version" class="form-label">{{ $t("Bark API Version") }}</label>
<select id="Bark API Version" v-model="$parent.notification.apiVersion" class="form-select" required>
<option value="v1">v1</option>
<option value="v2">v2</option>
</select>
</div>
<div class="mb-3">
<label for="Bark Endpoint" class="form-label">{{ $t("Bark Endpoint") }}<span style="color: red;"><sup>*</sup></span></label>
<input id="Bark Endpoint" v-model="$parent.notification.barkEndpoint" type="text" class="form-control" required>

View File

@@ -0,0 +1,7 @@
<template>
<div class="mb-3">
<label for="GrafanaOncallURL" class="form-label">{{ $t("GrafanaOncallURL") }}<span style="color: red;"><sup>*</sup></span></label>
<input id="GrafanaOncallURL" v-model="$parent.notification.GrafanaOncallURL" type="text" class="form-control" required>
</div>
</template>

View File

@@ -59,6 +59,28 @@
<input id="to-bcc" v-model="$parent.notification.smtpBCC" type="text" class="form-control" autocomplete="false" :required="!hasRecipient">
</div>
<p class="form-text">
<i18n-t tag="div" keypath="smtpLiquidIntroduction" class="form-text mb-3">
<a href="https://liquidjs.com/" target="_blank">{{ $t("documentation") }}</a>
</i18n-t>
<code v-pre>{{name}}</code>: {{ $t("emailTemplateServiceName") }}<br />
<code v-pre>{{msg}}</code>: {{ $t("emailTemplateMsg") }}<br />
<code v-pre>{{status}}</code>: {{ $t("emailTemplateStatus") }}<br />
<code v-pre>{{heartbeatJSON}}</code>: {{ $t("emailTemplateHeartbeatJSON") }}<b>{{ $t("emailTemplateLimitedToUpDownNotification") }}</b><br />
<code v-pre>{{monitorJSON}}</code>: {{ $t("emailTemplateMonitorJSON") }} <b>{{ $t("emailTemplateLimitedToUpDownNotification") }}</b><br />
<code v-pre>{{hostnameOrURL}}</code>: {{ $t("emailTemplateHostnameOrURL") }}<br />
</p>
<div class="mb-3">
<label for="subject-email" class="form-label">{{ $t("emailCustomSubject") }}</label>
<input id="subject-email" v-model="$parent.notification.customSubject" type="text" class="form-control" autocomplete="false" placeholder="">
<div class="form-text">{{ $t("leave blank for default subject") }}</div>
</div>
<div class="mb-3">
<label for="body-email" class="form-label">{{ $t("emailCustomBody") }}</label>
<textarea id="body-email" v-model="$parent.notification.customBody" type="text" class="form-control" autocomplete="false" placeholder=""></textarea>
<div class="form-text">{{ $t("leave blank for default body") }}</div>
</div>
<ToggleSection :heading="$t('smtpDkimSettings')">
<i18n-t tag="div" keypath="smtpDkimDesc" class="form-text mb-3">
<a href="https://nodemailer.com/dkim/" target="_blank">{{ $t("documentation") }}</a>
@@ -89,17 +111,6 @@
<input id="dkim-skip-fields" v-model="$parent.notification.smtpDkimskipFields" type="text" class="form-control" autocomplete="false" placeholder="message-id:date">
</div>
</ToggleSection>
<div class="mb-3">
<label for="subject-email" class="form-label">{{ $t("emailCustomSubject") }}</label>
<input id="subject-email" v-model="$parent.notification.customSubject" type="text" class="form-control" autocomplete="false" placeholder="">
<div v-pre class="form-text">
(leave blank for default one)<br />
{{NAME}}: Service Name<br />
{{HOSTNAME_OR_URL}}: Hostname or URL<br />
{{STATUS}}: Status<br />
</div>
</div>
</div>
</template>

View File

@@ -58,8 +58,6 @@
<script>
import HiddenInput from "../HiddenInput.vue";
import axios from "axios";
import { useToast } from "vue-toastification";
const toast = useToast();
export default {
components: {
@@ -68,7 +66,7 @@ export default {
methods: {
/**
* Get the URL for telegram updates
* @param {string} [mode=masked] Should the token be masked?
* @param {string} mode Should the token be masked?
* @returns {string} formatted URL
*/
telegramGetUpdatesURL(mode = "masked") {
@@ -85,7 +83,11 @@ export default {
return `https://api.telegram.org/bot${token}/getUpdates`;
},
/** Get the telegram chat ID */
/**
* Get the telegram chat ID
* @returns {void}
* @throws The chat ID could not be found
*/
async autoGetTelegramChatID() {
try {
let res = await axios.get(this.telegramGetUpdatesURL("withToken"));
@@ -106,7 +108,7 @@ export default {
}
} catch (error) {
toast.error(error.message);
this.$root.toastError(error.message);
}
},

View File

@@ -12,9 +12,7 @@
</div>
<div class="mb-3">
<label for="webhook-request-body" class="form-label">{{
$t("Request Body")
}}</label>
<label for="webhook-request-body" class="form-label">{{ $t("Request Body") }}</label>
<select
id="webhook-request-body"
v-model="$parent.notification.webhookContentType"
@@ -26,40 +24,29 @@
<option value="custom">{{ $t("webhookBodyCustomOption") }}</option>
</select>
<div class="form-text">
<div v-if="$parent.notification.webhookContentType == 'json'">
<p>{{ $t("webhookJsonDesc", ['"application/json"']) }}</p>
</div>
<div v-if="$parent.notification.webhookContentType == 'form-data'">
<i18n-t tag="p" keypath="webhookFormDataDesc">
<template #multipart>multipart/form-data"</template>
<template #decodeFunction>
<strong>json_decode($_POST['data'])</strong>
</template>
</i18n-t>
</div>
<div v-if="$parent.notification.webhookContentType == 'custom'">
<i18n-t tag="p" keypath="webhookCustomBodyDesc">
<template #msg>
<code>msg</code>
</template>
<template #heartbeat>
<code>heartbeatJSON</code>
</template>
<template #monitor>
<code>monitorJSON</code>
</template>
</i18n-t>
</div>
</div>
<div v-if="$parent.notification.webhookContentType == 'json'" class="form-text">{{ $t("webhookJsonDesc", ['"application/json"']) }}</div>
<i18n-t v-else-if="$parent.notification.webhookContentType == 'form-data'" tag="div" keypath="webhookFormDataDesc" class="form-text">
<template #multipart>multipart/form-data"</template>
<template #decodeFunction>
<strong>json_decode($_POST['data'])</strong>
</template>
</i18n-t>
<template v-else-if="$parent.notification.webhookContentType == 'custom'">
<i18n-t tag="div" keypath="liquidIntroduction" class="form-text">
<a href="https://liquidjs.com/" target="_blank">{{ $t("documentation") }}</a>
</i18n-t>
<code v-pre>{{msg}}</code>: {{ $t("templateMsg") }}<br />
<code v-pre>{{heartbeatJSON}}</code>: {{ $t("templateHeartbeatJSON") }} <b>({{ $t("templateLimitedToUpDownNotifications") }})</b><br />
<code v-pre>{{monitorJSON}}</code>: {{ $t("templateMonitorJSON") }} <b>({{ $t("templateLimitedToUpDownCertNotifications") }})</b><br />
<textarea
v-if="$parent.notification.webhookContentType == 'custom'"
id="customBody"
v-model="$parent.notification.webhookCustomBody"
class="form-control"
:placeholder="customBodyPlaceholder"
></textarea>
<textarea
id="customBody"
v-model="$parent.notification.webhookCustomBody"
class="form-control"
:placeholder="customBodyPlaceholder"
required
></textarea>
</template>
</div>
<div class="mb-3">
@@ -67,15 +54,14 @@
<input v-model="showAdditionalHeadersField" class="form-check-input" type="checkbox">
<label class="form-check-label">{{ $t("webhookAdditionalHeadersTitle") }}</label>
</div>
<div class="form-text">
<i18n-t tag="p" keypath="webhookAdditionalHeadersDesc"> </i18n-t>
</div>
<div class="form-text">{{ $t("webhookAdditionalHeadersDesc") }}</div>
<textarea
v-if="showAdditionalHeadersField"
id="additionalHeaders"
v-model="$parent.notification.webhookAdditionalHeaders"
class="form-control"
:placeholder="headersPlaceholder"
:required="showAdditionalHeadersField"
></textarea>
</div>
</template>
@@ -90,18 +76,18 @@ export default {
computed: {
headersPlaceholder() {
return this.$t("Example:", [
`
{
`{
"Authorization": "Authorization Token"
}`,
]);
},
customBodyPlaceholder() {
return `Example:
{
"Title": "Uptime Kuma Alert - {{ monitorJSON['name'] }}",
return this.$t("Example:", [
`{
"Title": "Uptime Kuma Alert{% if monitorJSON %} - {{ monitorJSON['name'] }}{% endif %}",
"Body": "{{ msg }}"
}`;
}`
]);
}
},
};

View File

@@ -12,6 +12,7 @@ import FreeMobile from "./FreeMobile.vue";
import GoogleChat from "./GoogleChat.vue";
import Gorush from "./Gorush.vue";
import Gotify from "./Gotify.vue";
import GrafanaOncall from "./GrafanaOncall.vue";
import HomeAssistant from "./HomeAssistant.vue";
import Kook from "./Kook.vue";
import Line from "./Line.vue";
@@ -54,7 +55,6 @@ import Splunk from "./Splunk.vue";
/**
* Manage all notification form.
*
* @type { Record<string, any> }
*/
const NotificationFormList = {
@@ -72,6 +72,7 @@ const NotificationFormList = {
"GoogleChat": GoogleChat,
"gorush": Gorush,
"gotify": Gotify,
"GrafanaOncall": GrafanaOncall,
"HomeAssistant": HomeAssistant,
"Kook": Kook,
"line": Line,

View File

@@ -72,8 +72,6 @@
<script>
import APIKeyDialog from "../../components/APIKeyDialog.vue";
import Confirm from "../Confirm.vue";
import { useToast } from "vue-toastification";
const toast = useToast();
export default {
components: {
@@ -96,6 +94,7 @@ export default {
/**
* Show dialog to confirm deletion
* @param {number} keyID ID of monitor that is being deleted
* @returns {void}
*/
deleteDialog(keyID) {
this.selectedKeyID = keyID;
@@ -104,19 +103,18 @@ export default {
/**
* Delete a key
* @returns {void}
*/
deleteKey() {
this.$root.deleteAPIKey(this.selectedKeyID, (res) => {
if (res.ok) {
toast.success(res.msg);
} else {
toast.error(res.msg);
}
this.$root.toastRes(res);
});
},
/**
* Show dialog to confirm pause
* @param {number} keyID ID of key to pause
* @returns {void}
*/
disableDialog(keyID) {
this.selectedKeyID = keyID;
@@ -124,7 +122,8 @@ export default {
},
/**
* Pause maintenance
* Pause API key
* @returns {void}
*/
disableKey() {
this.$root.getSocket().emit("disableAPIKey", this.selectedKeyID, (res) => {
@@ -133,7 +132,9 @@ export default {
},
/**
* Resume maintenance
* Resume API key
* @param {number} id Key to resume
* @returns {void}
*/
enableKey(id) {
this.$root.getSocket().emit("enableAPIKey", id, (res) => {

View File

@@ -1,228 +0,0 @@
<template>
<div>
<div class="my-4">
<div class="alert alert-warning" role="alert" style="border-radius: 15px;">
{{ $t("backupOutdatedWarning") }}<br />
<br />
{{ $t("backupRecommend") }}
</div>
<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="import-backend"
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: {
/**
* Show the confimation dialog confirming the configuration
* be imported
*/
confirmImport() {
this.$refs.confirmImport.show();
},
/** Download a backup of the configuration */
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();
},
/**
* Import the specified backup file
* @returns {?string}
*/
importBackup() {
this.processing = true;
let uploadItem = document.getElementById("import-backend").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 {
#import-backend {
&::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>

View File

@@ -293,16 +293,25 @@ export default {
},
methods: {
/** Save the settings */
/**
* Save the settings
* @returns {void}
*/
saveGeneral() {
localStorage.timezone = this.$root.userTimezone;
this.saveSettings();
},
/** Get the base URL of the application */
/**
* Get the base URL of the application
* @returns {void}
*/
autoGetPrimaryBaseURL() {
this.settings.primaryBaseURL = location.protocol + "//" + location.host;
},
/**
* Test the chrome executable
* @returns {void}
*/
testChrome() {
this.$root.getSocket().emit("testChrome", this.settings.chromeExecutable, (res) => {
this.$root.toastRes(res);

View File

@@ -57,9 +57,6 @@
<script>
import Confirm from "../../components/Confirm.vue";
import { log } from "../../util.ts";
import { useToast } from "vue-toastification";
const toast = useToast();
export default {
components: {
@@ -94,7 +91,10 @@ export default {
},
methods: {
/** Get the current size of the database */
/**
* Get the current size of the database
* @returns {void}
*/
loadDatabaseSize() {
log.debug("monitorhistory", "load database size");
this.$root.getSocket().emit("getDatabaseSize", (res) => {
@@ -107,30 +107,39 @@ export default {
});
},
/** Request that the database is shrunk */
/**
* Request that the database is shrunk
* @returns {void}
*/
shrinkDatabase() {
this.$root.getSocket().emit("shrinkDatabase", (res) => {
if (res.ok) {
this.loadDatabaseSize();
toast.success("Done");
this.$root.toastSuccess("Done");
} else {
log.debug("monitorhistory", res);
}
});
},
/** Show the dialog to confirm clearing stats */
/**
* Show the dialog to confirm clearing stats
* @returns {void}
*/
confirmClearStatistics() {
this.$refs.confirmClearStatistics.show();
},
/** Send the request to clear stats */
/**
* Send the request to clear stats
* @returns {void}
*/
clearStatistics() {
this.$root.clearStatistics((res) => {
if (res.ok) {
this.$router.go();
} else {
toast.error(res.msg);
this.$root.toastError(res.msg);
}
});
},

View File

@@ -20,6 +20,39 @@
</button>
</div>
<div class="my-4 pt-4">
<h5 class="my-4 settings-subheading">{{ $t("monitorToastMessagesLabel") }}</h5>
<p>{{ $t("monitorToastMessagesDescription") }}</p>
<div class="my-4">
<label for="toastErrorTimeoutSecs" class="form-label">
{{ $t("toastErrorTimeout") }}
</label>
<input
id="toastErrorTimeoutSecs"
v-model="toastErrorTimeoutSecs"
type="number"
class="form-control"
min="-1"
step="1"
/>
</div>
<div class="my-4">
<label for="toastSuccessTimeoutSecs" class="form-label">
{{ $t("toastSuccessTimeout") }}
</label>
<input
id="toastSuccessTimeoutSecs"
v-model="toastSuccessTimeoutSecs"
type="number"
class="form-control"
min="-1"
step="1"
/>
</div>
</div>
<div class="my-4 pt-4">
<h5 class="my-4 settings-subheading">{{ $t("settingsCertificateExpiry") }}</h5>
<p>{{ $t("certificationExpiryDescription") }}</p>
@@ -58,6 +91,8 @@ export default {
data() {
return {
toastSuccessTimeoutSecs: 20,
toastErrorTimeoutSecs: -1,
/**
* Variable to store the input for new certificate expiry day.
*/
@@ -77,10 +112,31 @@ export default {
},
},
watch: {
// Parse, store and apply new timeout settings.
toastSuccessTimeoutSecs(newTimeout) {
const parsedTimeout = parseInt(newTimeout);
if (parsedTimeout != null && !Number.isNaN(parsedTimeout)) {
localStorage.toastSuccessTimeout = newTimeout > 0 ? newTimeout * 1000 : newTimeout;
}
},
toastErrorTimeoutSecs(newTimeout) {
const parsedTimeout = parseInt(newTimeout);
if (parsedTimeout != null && !Number.isNaN(parsedTimeout)) {
localStorage.toastErrorTimeout = newTimeout > 0 ? newTimeout * 1000 : newTimeout;
}
}
},
mounted() {
this.loadToastTimeoutSettings();
},
methods: {
/**
* Remove a day from expiry notification days.
* @param {number} day The day to remove.
* @returns {void}
*/
removeExpiryNotifDay(day) {
this.settings.tlsExpiryNotifyDays = this.settings.tlsExpiryNotifyDays.filter(d => d !== day);
@@ -93,6 +149,7 @@ export default {
* - day is > 0.
* - The day is not already in the list.
* @param {number} day The day number to add.
* @returns {void}
*/
addExpiryNotifDay(day) {
if (day != null && day !== "") {
@@ -106,6 +163,28 @@ export default {
}
}
},
/**
* Loads toast timeout settings from storage to component data.
* @returns {void}
*/
loadToastTimeoutSettings() {
const successTimeout = localStorage.toastSuccessTimeout;
if (successTimeout !== undefined) {
const parsedTimeout = parseInt(successTimeout);
if (parsedTimeout != null && !Number.isNaN(parsedTimeout)) {
this.toastSuccessTimeoutSecs = parsedTimeout > 0 ? parsedTimeout / 1000 : parsedTimeout;
}
}
const errorTimeout = localStorage.toastErrorTimeout;
if (errorTimeout !== undefined) {
const parsedTimeout = parseInt(errorTimeout);
if (parsedTimeout != null && !Number.isNaN(parsedTimeout)) {
this.toastErrorTimeoutSecs = parsedTimeout > 0 ? parsedTimeout / 1000 : parsedTimeout;
}
}
},
},
};
</script>

View File

@@ -175,17 +175,26 @@ export default {
this.$root.getSocket().emit(prefix + "leave");
},
methods: {
/** Start the Cloudflare tunnel */
/**
* Start the Cloudflare tunnel
* @returns {void}
*/
start() {
this.$root.getSocket().emit(prefix + "start", this.cloudflareTunnelToken);
},
/** Stop the Cloudflare tunnel */
/**
* Stop the Cloudflare tunnel
* @returns {void}
*/
stop() {
this.$root.getSocket().emit(prefix + "stop", this.currentPassword, (res) => {
this.$root.toastRes(res);
});
},
/** Remove the token for the Cloudflare tunnel */
/**
* Remove the token for the Cloudflare tunnel
* @returns {void}
*/
removeToken() {
this.$root.getSocket().emit(prefix + "removeToken");
this.cloudflareTunnelToken = "";

View File

@@ -155,7 +155,10 @@ export default {
},
methods: {
/** Check new passwords match before saving them */
/**
* Check new passwords match before saving them
* @returns {void}
*/
savePassword() {
if (this.password.newPassword !== this.password.repeatNewPassword) {
this.invalidPassword = true;
@@ -173,7 +176,10 @@ export default {
}
},
/** Disable authentication for web app access */
/**
* Disable authentication for web app access
* @returns {void}
*/
disableAuth() {
this.settings.disableAuth = true;
@@ -186,7 +192,10 @@ export default {
}, this.password.currentPassword);
},
/** Enable authentication for web app access */
/**
* Enable authentication for web app access
* @returns {void}
*/
enableAuth() {
this.settings.disableAuth = false;
this.saveSettings();
@@ -194,7 +203,10 @@ export default {
location.reload();
},
/** Show confirmation dialog for disable auth */
/**
* Show confirmation dialog for disable auth
* @returns {void}
*/
confirmDisableAuth() {
this.$refs.confirmDisableAuth.show();
},

View File

@@ -28,11 +28,9 @@
</template>
<script>
import { useToast } from "vue-toastification";
import TagEditDialog from "../../components/TagEditDialog.vue";
import Tag from "../Tag.vue";
import Confirm from "../Confirm.vue";
const toast = useToast();
export default {
components: {
@@ -86,7 +84,7 @@ export default {
if (res.ok) {
this.tagsList = res.tags;
} else {
toast.error(res.msg);
this.$root.toastError(res.msg);
}
});
},
@@ -138,7 +136,7 @@ export default {
/**
* Get monitors which has a specific tag locally
* @param {number} tagId id of the tag to filter
* @returns {Object[]} list of monitors which has a specific tag
* @returns {object[]} list of monitors which has a specific tag
*/
monitorsByTag(tagId) {
return Object.values(this.$root.monitorList).filter((monitor) => {

View File

@@ -1,4 +1,6 @@
# How to translate
# Translations
## How to translate
(2023-01-24 Updated)
@@ -7,7 +9,7 @@
3. Make sure your GitHub email is matched with Weblate's account, so that it could show you as a contributor on GitHub
4. Choose your language on Weblate and start translating.
# How to add a new language in the dropdown
## How to add a new language in the dropdown
1. Add your language at https://weblate.kuma.pet/projects/uptime-kuma/uptime-kuma/
2. Find the language code (You can find it at the end of the URL)

View File

@@ -63,7 +63,7 @@
"Add one": "أضف واحدا",
"wayToGetCloudflaredURL": "(قم بتنزيل CloudFlared من {0})",
"cloudflareWebsite": "موقع CloudFlare",
"Message:": ":رسالة",
"Message:": "رسالة:",
"Don't know how to get the token? Please read the guide:": "لا أعرف كيفية الحصول على الرمز المميز؟ يرجى قراءة الدليل:",
"telegramSendSilently": "أرسل بصمت",
"telegramSendSilentlyDescription": "ترسل الرسالة بصمت ويتلقى المستخدمون إشعارا بدون صوت.",
@@ -134,7 +134,7 @@
"ignoreTLSError": "تجاهل خطأ TLS/SSL لمواقع HTTPS",
"upsideDownModeDescription": "اقلب الحالة رأسًا على عقب. إذا كانت الخدمة قابلة للوصول إلى أسفل.",
"maxRedirectDescription": "الحد الأقصى لعدد إعادة التوجيه لمتابعة. ضبط على 0 لتعطيل إعادة التوجيه.",
"Upside Down Mode": "وضع أسفل أسفل",
"Upside Down Mode": "وضع رأسا على عقب",
"Max. Redirects": "الأعلى. إعادة التوجيه",
"Accepted Status Codes": "رموز الحالة المقبولة",
"Push URL": "دفع عنوان URL",
@@ -209,7 +209,7 @@
"Indigo": "النيلي",
"Purple": "نفسجي",
"webhookAdditionalHeadersDesc": "يحدد رؤوس إضافية مرسلة مع webhook.",
"Webhook URL": "Webhook URL",
"Webhook URL": "عنوان URL للخطاف الإلكتروني",
"Pink": "لون القرنفل",
"Custom": "العادة",
"Status Pages": "صفحات الحالة",
@@ -359,10 +359,10 @@
"Connection String": "سلسلة الاتصال",
"Query": "استفسار",
"settingsCertificateExpiry": "شهادة TLS انتهاء الصلاحية",
"certificationExpiryDescription": "شاشات HTTPS تضيء عندما تنتهي شهادة TLS في",
"certificationExpiryDescription": "تقوم مراقبات HTTPS بتشغيل إشعار عند انتهاء صلاحية شهادة TLS في:",
"Setup Docker Host": "إعداد مضيف Docker",
"Connection Type": "نوع الاتصال",
"Docker Daemon": "Docker Daemon",
"Docker Daemon": "دُوكر Daemon",
"deleteDockerHostMsg": "هل أنت متأكد من حذف مضيف Docker لجميع الشاشات؟",
"socket": "قابس كهرباء",
"tcp": "TCP / HTTP",
@@ -378,20 +378,20 @@
"Chat ID": "معرف الدردشة",
"telegramMessageThreadID": "معرف المواضيع",
"supportTelegramChatID": "دعم الدردشة المباشرة / معرف الدردشة للقناة",
"wayToGetTelegramChatID": "يمكنك الحصول على معرف الدردشة الخاص بك عن طريق إرسال رسالة إلى الروبوت والانتقال إلى عنوان URL هذا لعرض Chat_id",
"wayToGetTelegramChatID": "يمكنك الحصول على معرف الدردشة الخاص بك عن طريق إرسال رسالة إلى البوت والانتقال إلى عنوان URL هذا لعرض chat_id:",
"YOUR BOT TOKEN HERE": "رمز الروبوت الخاص بك هنا",
"chatIDNotFound": "لم يتم العثور على معرف الدردشة ؛ الرجاء إرسال رسالة إلى هذا الروبوت أولاً",
"disableCloudflaredNoAuthMsg": "أنت في وضع مصادقة لا توجد كلمة مرور غير مطلوبة.",
"trustProxyDescription": "ثق في رؤوس \"X-Forwarded- *\". إذا كنت ترغب في الحصول على عنوان IP الصحيح للعميل وكان Uptime Kuma خلف وكيل مثل Nginx أو Apache ، فيجب عليك تمكين هذا.",
"wayToGetLineNotifyToken": "يمكنك الحصول على رمز الوصول من {0}",
"Examples": "أمثلة",
"Home Assistant URL": "Home Assistant URL",
"Home Assistant URL": "عنوان URL لـ Home Assistant",
"Long-Lived Access Token": "الرمز المميز للوصول منذ فترة طويلة",
"Long-Lived Access Token can be created by clicking on your profile name (bottom left) and scrolling to the bottom then click Create Token. ": "يمكن إنشاء رمز الوصول منذ فترة طويلة عن طريق النقر على اسم ملف التعريف الخاص بك (أسفل اليسار) والتمرير إلى الأسفل ثم انقر فوق إنشاء الرمز المميز. ",
"Notification Service": "خدمة الإخطار",
"default: notify all devices": "الافتراضي: إخطار جميع الأجهزة",
"A list of Notification Services can be found in Home Assistant under \"Developer Tools > Services\" search for \"notification\" to find your device/phone name.": "يمكن العثور على قائمة بخدمات الإخطار في المساعد المنزلي ضمن \"Developer Tools > Services\" ابحث عن \"notification\" للعثور على اسم جهازك/هاتفك.",
"Automations can optionally be triggered in Home Assistant:": "يمكن تشغيل الأتمتة اختياريًا في Home Assistant:",
"Automations can optionally be triggered in Home Assistant:": "الاتمته يمكن اختياريا تحفيزها عبر Home Assistant:",
"Trigger type:": "نوع الزناد:",
"Event type:": "نوع الحدث:",
"Event data:": "بيانات الحدث:",
@@ -430,7 +430,7 @@
"Display Timezone": "عرض المنطقة الزمنية",
"Server Timezone": "المنطقة الزمنية الخادم",
"statusPageMaintenanceEndDate": "نهاية",
"IconUrl": "url url icon",
"IconUrl": "عنوان URL للرمز",
"Enable DNS Cache": "تمكين ذاكرة التخزين المؤقت DNS",
"Disable": "إبطال",
"dnsCacheDescription": "قد لا يعمل في بعض بيئات IPv6 تعطيله إذا واجهت أي مشكلات.",
@@ -456,7 +456,7 @@
"To Email": "للبريد الإلكتروني",
"smtpCC": "نسخة",
"smtpBCC": "BCC",
"Discord Webhook URL": "Discord Webhook URL",
"Discord Webhook URL": "عنوان URL للخّطاف على الويب للديسكورد",
"wayToGetDiscordURL": "يمكنك الحصول على هذا بالانتقال إلى إعدادات الخادم -> عمليات التكامل -> عرض الخطافات على الويب -> خطاف ويب جديد",
"Bot Display Name": "اسم عرض الروبوت",
"Prefix Custom Message": "بادئة رسالة مخصصة",
@@ -464,7 +464,7 @@
"wayToGetTeamsURL": "يمكنك معرفة كيفية إنشاء عنوان URL webhook {0}.",
"wayToGetZohoCliqURL": "يمكنك معرفة كيفية إنشاء عنوان URL webhook {0}.",
"needSignalAPI": "تحتاج إلى وجود عميل إشارة مع REST API.",
"wayToCheckSignalURL": "يمكنك التحقق من عنوان URL هذا لعرض كيفية إعداد واحد",
"wayToCheckSignalURL": "يمكنك التحقق من عنوان URL هذا لعرض كيفية إعداد واحد:",
"Number": "رقم",
"Recipients": "المستلمين",
"Access Token": "رمز وصول",
@@ -477,8 +477,8 @@
"User ID": "معرف المستخدم",
"Messaging API": "واجهة برمجة تطبيقات المراسلة",
"wayToGetLineChannelToken": "قم أولاً بالوصول إلى {0} إنشاء مزود وقناة (واجهة برمجة تطبيقات المراسلة) ، ثم يمكنك الحصول على رمز الوصول إلى القناة ومعرف المستخدم من عناصر القائمة المذكورة أعلاه.",
"Icon URL": "url url icon",
"aboutIconURL": "يمكنك توفير رابط لصورة في \"Icon URL\" لتجاوز صورة الملف الشخصي الافتراضي. لن يتم استخدامه إذا تم تعيين رمز رمز رمز.",
"Icon URL": "عنوان URL للرمز",
"aboutIconURL": "يمكنك توفير رابط لصورة في \"رمز URL\" لتجاوز صورة الملف الشخصي الافتراضية. لن يتم استخدامه إذا تم تعيين ايقونة اموجي.",
"aboutMattermostChannelName": "يمكنك تجاوز القناة الافتراضية التي تنشرها WebHook من خلال إدخال اسم القناة في \"Channel Name\" الحقل. يجب تمكين هذا في إعدادات Webhook Mattern. السابق",
"dataRetentionTimeError": "يجب أن تكون فترة الاستبقاء 0 أو أكبر",
"infiniteRetention": "ضبط على 0 للاحتفاظ لا نهائي.",
@@ -492,7 +492,7 @@
"clearEventsMsg": "هل أنت متأكد من حذف جميع الأحداث لهذا الشاشة؟",
"clearHeartbeatsMsg": "هل أنت متأكد من حذف جميع دقات القلب لهذا الشاشة؟",
"confirmImportMsg": "هل أنت متأكد من أنك تريد استيراد النسخ الاحتياطي؟ يرجى التحقق من أنك حددت خيار الاستيراد الصحيح.",
"twoFAVerifyLabel": "الرجاء إدخال الرمز المميز الخاص بك للتحقق من 2FA",
"twoFAVerifyLabel": "يرجى إدخال الرمز المميز الخاص بك للتحقق من المصادقة الثنائية (2FA):",
"pushoversounds pushover": "سداد (افتراضي)",
"pushoversounds bike": "دراجة هوائية",
"pushoversounds bugle": "بوق",
@@ -550,7 +550,7 @@
"SMS Type": "نوع الرسائل القصيرة",
"octopushTypePremium": "قسط (سريع - موصى به للتنبيه)",
"octopushTypeLowCost": "التكلفة المنخفضة (بطيئة - تم حظرها أحيانًا بواسطة المشغل)",
"checkPrice": "تحقق من الأسعار {0}",
"checkPrice": "تحقق من أسعار {0}:",
"apiCredentials": "بيانات اعتماد API",
"octopushLegacyHint": "هل تستخدم الإصدار القديم من Octopush (2011-2020) أو الإصدار الجديد؟",
"Check octopush prices": "تحقق من أسعار Octopush {0}.",
@@ -581,7 +581,7 @@
"Base URL": "عنوان URL الأساسي",
"goAlertInfo": "الهدف هو تطبيق مفتوح المصدر لجدولة الجدولة التلقائية والإشعارات (مثل الرسائل القصيرة أو المكالمات الصوتية). إشراك الشخص المناسب تلقائيًا بالطريقة الصحيحة وفي الوقت المناسب! {0}",
"goAlertIntegrationKeyInfo": "احصل على مفتاح تكامل API العام للخدمة في هذا التنسيق \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\" عادةً قيمة المعلمة الرمزية لعنوان url المنسق.",
"TemplateCode": "TemplateCode",
"TemplateCode": "رمز القالب",
"SignName": "اسم تسجيل الدخول",
"Sms template must contain parameters: ": "يجب أن يحتوي قالب الرسائل القصيرة على معلمات: ",
"Bark Endpoint": "نقطة نهاية اللحاء",
@@ -613,10 +613,10 @@
"Feishu WebHookUrl": "Feishu Webhookurl",
"matrixHomeserverURL": "عنوان URL HomeServer (مع HTTP (S)",
"Internal Room Id": "معرف الغرفة الداخلية",
"matrixDesc1": "يمكنك العثور على معرف الغرفة الداخلي من خلال البحث في القسم المتقدم من إعدادات الغرفة في عميل Matrix الخاص بك. يجب أن تبدو مثل! QMDRCPUIFLWSFJXYE6",
"matrixDesc1": "يمكنك العثور على معرف الغرفة الداخلي من خلال البحث في القسم المتقدم لإعدادات الغرفة في عميل Matrix الخاص بك. ينبغي أن يبدو مثل QMdRCpUIfLwsfjxye6:home.server!.",
"Uptime Kuma URL": "UPTIME KUMA URL",
"Icon Emoji": "أيقونة الرموز التعبيرية",
"signalImportant": "مهم",
"signalImportant": "هام: لا يمكنك المزج بين المجموعات والأرقام في المستلمين!",
"aboutWebhooks": "مزيد من المعلومات حول Webhooks ON",
"aboutChannelName": "أدخل اسم القناة في حقل اسم القناة {0} إذا كنت تريد تجاوز قناة WebHook. السابق",
"aboutKumaURL": "إذا تركت حقل URL في وقت التشغيل KUMA فارغًا ، فسيتم افتراضيًا إلى صفحة GitHub Project.",
@@ -679,10 +679,13 @@
"deleteAPIKeyMsg": "هل أنت متأكد أنك تريد حذف مفتاح API هذا؟",
"Auto Get": "الحصول التلقائي",
"Auto resolve or acknowledged": "",
"backupDescription2": "ملحوظة",
"backupDescription2": "ملحوظة: لم يتم تضمين بيانات السجل والأحداث.",
"languageName": "العربية",
"Game": "الألعاب",
"List": "القائمة",
"statusMaintenance": "الصيانة",
"Home": "الرئيسة"
"Home": "الرئيسة",
"setupDatabaseChooseDatabase": "ما هي قاعدة البيانات التي تريد استخدامها؟",
"setupDatabaseEmbeddedMariaDB": "لا تحتاج إلى تعيين أي شيء. قامت نسخة دُوكر بتضمين MariaDB لك تلقائيًا. سيتصل (آب تايم كارما) بقاعدة البيانات هذه عبر مقبس Unix.",
"setupDatabaseMariaDB": "للاتصال بقاعدة بيانات MariaDB خارجية. تحتاج إلى تعيين معلومات اتصال قاعدة البيانات."
}

View File

@@ -644,7 +644,7 @@
"IconUrl": "Икона URL адрес",
"webhookAdditionalHeadersTitle": "Допълнителни хедъри",
"webhookAdditionalHeadersDesc": "Задава допълнителни хедъри, изпратени с уеб куката. Всеки хедър трябва да бъде дефиниран като JSON ключ/стойност.",
"Enable DNS Cache": "Активирай DNS кеширане за HTTP(S) монитори",
"Enable DNS Cache": "(Отпаднала) Активирай DNS кеширане за HTTP(S) монитори",
"Enable": "Активирай",
"Disable": "Деактивирай",
"dnsCacheDescription": "Възможно е да не работи в IPv6 среда - деактивирайте, ако срещнете проблеми.",
@@ -838,5 +838,37 @@
"styleElapsedTimeShowNoLine": "Покажи (без ред)",
"gamedigGuessPort": "Gamedig: Познай порт",
"gamedigGuessPortDescription": "Портът, използван от Valve Server Query Protocol, може да е различен от клиентския порт. Опитайте това, ако мониторът не може да се свърже с вашия сървър.",
"styleElapsedTimeShowWithLine": "Покажи (с ред)"
"styleElapsedTimeShowWithLine": "Покажи (с ред)",
"enableNSCD": "Активирай NSCD (Name Service Cache Daemon) за кеширане на всички DNS заявки",
"dbName": "Име на базата данни",
"setupDatabaseChooseDatabase": "Коя база данни желаете да използвате?",
"Saved.": "Запазено.",
"toastErrorTimeout": "Време за изчакване на известията при грешка",
"toastSuccessTimeout": "Време за изчакване на известията при успех",
"monitorToastMessagesDescription": "Известието за състояние на монитора изчезва след определено време в секунди. Задаване на -1, деактивира времето за изчакване. Задаване на 0 деактивира тост известията.",
"monitorToastMessagesLabel": "Мониторинг на известията при промяна на състоянието",
"setupDatabaseEmbeddedMariaDB": "Не е нужно да настройвате нищо. Този Docker имидж автоматично е вградил и конфигурирал MariaDB за Вас. Uptime Kuma ще се свърже с тази база данни чрез Unix сокет.",
"setupDatabaseMariaDB": "Свързване към външна MariaDB база данни. Трябва да зададете информацията за връзка с базата данни.",
"setupDatabaseSQLite": "Обикновен файл с база данни, препоръчително при маломащабен тип внедрявания. Преди v2.0.0 Uptime Kuma използва SQLite като база данни по подразбиране.",
"Bark API Version": "Версия на Bark API",
"pushViewCode": "Как да използвате Push монитор? (Вижте кода)",
"pushOthers": "Други",
"programmingLanguages": "Програмни езици",
"authInvalidToken": "Невалиден токен.",
"authUserInactiveOrDeleted": "Потребителят е неактивен или изтрит.",
"authIncorrectCreds": "Неправилно потребителско име или парола.",
"2faAlreadyEnabled": "2FA вече е активирано.",
"2faEnabled": "2FA е активирано.",
"2faDisabled": "2FA е деактивирано.",
"successAdded": "Добавен успешно.",
"successPaused": "Успешно поставен на пауза.",
"successDeleted": "Успешно изтрит.",
"successEdited": "Успешно редактиран.",
"successBackupRestored": "Резервното копие е възстановено успешно.",
"successDisabled": "Успешно деактивиран.",
"successEnabled": "Успешно активиран.",
"tagNotFound": "Етикетът не е намерен.",
"successResumed": "Успешно възобновен.",
"successAuthChangePassword": "Паролата е актуализирана успешно.",
"foundChromiumVersion": "Намерен Chromium/Chrome. Версия: {0}"
}

View File

@@ -653,7 +653,7 @@
"Server Timezone": "Časové pásmo serveru",
"statusPageMaintenanceEndDate": "Konec",
"IconUrl": "Adresa URL ikony",
"Enable DNS Cache": "Povolit DNS Cache pro HTTP(s) dohledy",
"Enable DNS Cache": "(Zastaralé) Povolit DNS Cache pro HTTP(s) dohledy",
"Enable": "Povolit",
"Disable": "Zakázat",
"dnsCacheDescription": "V některých IPv6 prostředích nemusí fungovat. Pokud narazíte na nějaké problémy, tuto možnost vypněte.",
@@ -838,5 +838,37 @@
"styleElapsedTimeShowWithLine": "Zobrazit (s linkou)",
"gamedigGuessPortDescription": "Port používaný protokolem Valve Server Query Protocol se může lišit od portu klienta. Pokud se monitor nemůže připojit k serveru, zkuste to.",
"styleElapsedTimeShowNoLine": "Zobrazit (bez linky)",
"gamedigGuessPort": "Gamedig: Guess Port"
"gamedigGuessPort": "Gamedig: Guess Port",
"Saved.": "Uloženo.",
"setupDatabaseChooseDatabase": "Kterou databázi chcete použít?",
"setupDatabaseEmbeddedMariaDB": "Nemusíte nic nastavovat. Tento Docker obraz pro vás automaticky vložil a nakonfiguroval databázi MariaDB. Uptime Kuma se k této databázi připojí prostřednictvím unixového socketu.",
"setupDatabaseMariaDB": "Připojení k externí databázi MariaDB. Je třeba nastavit informace o připojení k databázi.",
"setupDatabaseSQLite": "Jednoduchý databázový soubor, doporučený pro malé instalace. Před verzí 2.0.0 používal Uptime Kuma jako výchozí databázi SQLite.",
"dbName": "Název databáze",
"enableNSCD": "Povolit NSCD (Name Service Cache Daemon) pro cachování všech DNS požadavků",
"toastErrorTimeout": "Časový limit pro oznámení o chybách",
"toastSuccessTimeout": "Časový limit pro oznámení o úspěchu",
"Bark API Version": "Verze API Bark",
"authInvalidToken": "Neplatný token.",
"2faAlreadyEnabled": "2FA je již aktivní.",
"2faEnabled": "2FA aktivní.",
"2faDisabled": "2FA neaktivní.",
"successResumed": "Úspěšně obnoveno.",
"successPaused": "Úspěšně pozastaveno.",
"successDeleted": "Úspěšně smazáno.",
"successEdited": "Úspěšně upraveno.",
"successBackupRestored": "Záloha byla úspěšně obnovena.",
"successDisabled": "Úspěšně zakázáno.",
"successEnabled": "Úspěšně povoleno.",
"tagNotFound": "Štítek nebyl nalezen.",
"foundChromiumVersion": "Nalezeno Chromium/Chrom. Verze: {0}",
"authUserInactiveOrDeleted": "Uživatel je neaktivní nebo smazaný.",
"authIncorrectCreds": "Nesprávné uživatelské jméno nebo heslo.",
"successAdded": "Úspěšně přidáno.",
"successAuthChangePassword": "Heslo bylo úspěšně aktualizováno.",
"pushOthers": "Ostatní",
"programmingLanguages": "Programovací jazyky",
"monitorToastMessagesLabel": "Upozornění Monitor Toast",
"monitorToastMessagesDescription": "Upozornění Toast zmizí po uplynutí nastaveného času. Časový limit vypnete nastavením -1. Upozornění vypnete nastavením 0.",
"pushViewCode": "Jak používat Push monitor? (Zobrazit kód)"
}

View File

@@ -1,5 +1,5 @@
{
"languageName": "Danish (Danmark)",
"languageName": "Dansk",
"Settings": "Indstillinger",
"Dashboard": "Betjeningspanel",
"New Update": "Opdatering tilgængelig",
@@ -8,10 +8,10 @@
"Theme": "Tema",
"General": "Generelt",
"Version": "Version",
"Check Update On GitHub": "Tjek efter opdateringer på Github",
"Check Update On GitHub": "Tjek efter opdateringer på GitHub",
"List": "Liste",
"Add": "Tilføj",
"Add New Monitor": "Tilføj ny Overvåger",
"Add New Monitor": "Tilføj ny overvågning",
"Quick Stats": "Oversigt",
"Up": "Aktiv",
"Down": "Inaktiv",
@@ -25,7 +25,7 @@
"Message": "Beskeder",
"No important events": "Ingen vigtige begivenheder",
"Resume": "Fortsæt",
"Edit": "Rediger",
"Edit": "Redigér",
"Delete": "Slet",
"Current": "Aktuelt",
"Uptime": "Oppetid",
@@ -37,7 +37,7 @@
"checkEverySecond": "Tjek hvert {0} sekund",
"Response": "Respons",
"Ping": "Ping",
"Monitor Type": "Overvåger type",
"Monitor Type": "Overvågningstype",
"Keyword": "Nøgleord",
"Friendly Name": "Visningsnavn",
"URL": "URL",
@@ -47,7 +47,7 @@
"Retries": "Gentagelser",
"retriesDescription": "Maksimalt antal gentagelser, før tjenesten markeres som inaktiv og sender en meddelelse.",
"Advanced": "Avanceret",
"ignoreTLSError": "Ignorere TLS/SSL web fejl",
"ignoreTLSError": "Ignorér TLS/SSL fejl for HTTPS websteder",
"Upside Down Mode": "Omvendt tilstand",
"upsideDownModeDescription": "Håndter tilstanden omvendt. Hvis tjenesten er tilgængelig, vises den som inaktiv.",
"Max. Redirects": "Maks. Omdirigeringer",
@@ -69,17 +69,17 @@
"Search Engine Visibility": "Søgemaskine synlighed",
"Allow indexing": "Tillad indeksering",
"Discourage search engines from indexing site": "Frabed søgemaskiner at indeksere webstedet",
"Change Password": "Ændre adgangskode",
"Change Password": "Skift adgangskode",
"Current Password": "Nuværende adgangskode",
"New Password": "Ny adgangskode",
"Repeat New Password": "Gentag den nye adgangskode",
"passwordNotMatchMsg": "Adgangskoderne er ikke ens.",
"Update Password": "Opdater adgangskode",
"Disable Auth": "Deaktiver autentificering",
"Enable Auth": "Aktiver autentificering",
"Update Password": "Opdatér adgangskode",
"Disable Auth": "Deaktivér autentifikation",
"Enable Auth": "Aktivér autentifikation",
"Logout": "Log ud",
"notificationDescription": "Tildel underretninger til Overvåger(e), så denne funktion træder i kraft.",
"Leave": "Verlassen",
"Leave": "Forlad",
"I understand, please disable": "Jeg er indforstået, deaktiver venligst",
"Confirm": "Bekræft",
"Yes": "Ja",
@@ -88,7 +88,7 @@
"Password": "Adgangskode",
"Remember me": "Husk mig",
"Login": "Log ind",
"No Monitors, please": "Ingen Overvågere",
"No Monitors, please": "Ingen overvågninger",
"add one": "tilføj en",
"Notification Type": "Underretningstype",
"Email": "E-Mail",
@@ -101,7 +101,7 @@
"Resolver Server": "Navne-server",
"rrtypeDescription": "Vælg den type RR, du vil overvåge.",
"Last Result": "Seneste resultat",
"pauseMonitorMsg": "Er du sikker på, at du vil standse Overvågeren?",
"pauseMonitorMsg": "Er du sikker på at du vil standse overvågningen?",
"Create your admin account": "Opret din administratorkonto",
"Repeat Password": "Gentag adgangskoden",
"Resource Record Type": "Resource Record Type",
@@ -130,14 +130,14 @@
"confirmEnableTwoFAMsg": "Er du sikker på at du vil aktivere 2FA?",
"confirmDisableTwoFAMsg": "Er du sikker på at du vil deaktivere 2FA?",
"Apply on all existing monitors": "Anvend på alle eksisterende overvågere",
"Verify Token": "Verificere Token",
"Verify Token": "Bekræft nøgle",
"Setup 2FA": "Opsæt 2FA",
"Enable 2FA": "Aktiver 2FA",
"Disable 2FA": "Deaktiver 2FA",
"2FA Settings": "2FA Indstillinger",
"Two Factor Authentication": "To-Faktor Autentificering",
"Active": "Aktive",
"Inactive": "Inaktive",
"Active": "Aktiv",
"Inactive": "Inaktiv",
"Token": "Token",
"Show URI": "Vis URI",
"Clear all statistics": "Ryd alle Statistikker",
@@ -145,16 +145,16 @@
"importHandleDescription": "Vælg 'Spring over eksisterende', hvis du vil springe over hver overvåger eller underretning med samme navn. 'Overskriv' sletter alle eksisterende overvågere og underretninger.",
"confirmImportMsg": "Er du sikker på at importere sikkerhedskopien? Sørg for, at du har valgt den rigtige importindstilling.",
"Heartbeat Retry Interval": "Hjerteslag gentagelsesinterval",
"Import Backup": "Importer Backup",
"Export Backup": "Eksporter Backup",
"Import Backup": "Importér Backup",
"Export Backup": "Eksportér Backup",
"Skip existing": "Spring over eksisterende",
"Overwrite": "Overskriv",
"Options": "Valgmuligheder",
"Keep both": "Behold begge",
"Tags": "Etiketter",
"Add New below or Select...": "Tilføj Ny nedenfor eller Vælg…",
"Tag with this name already exist.": "Et Tag med dette navn findes allerede.",
"Tag with this value already exist.": "Et Tag med denne værdi findes allerede.",
"Tag with this name already exist.": "En etiket med dette navn findes allerede.",
"Tag with this value already exist.": "En etiket med denne værdi findes allerede.",
"color": "farve",
"value (optional)": "værdi (valgfri)",
"Gray": "Grå",
@@ -175,8 +175,8 @@
"Partially Degraded Service": "Delvist forringet service",
"Degraded Service": "Forringet service",
"Add Group": "Tilføj Gruppe",
"Add a monitor": "Tilføj en Overvåger",
"Edit Status Page": "Rediger Statusside",
"Add a monitor": "Tilføj en overvågning",
"Edit Status Page": "Redigér Statusside",
"Go to Dashboard": "Gå til Betjeningspanel",
"Status Page": "Statusside",
"Status Pages": "Statusside",
@@ -228,7 +228,7 @@
"wayToGetDiscordURL": "Du kan få dette ved at gå til Serverindstillinger -> Integrationer -> Opret webhook",
"Bot Display Name": "Bot Visningsnavn",
"Prefix Custom Message": "Præfiks Brugerdefineret Besked",
"Hello @everyone is...": "Hello {'@'}everyone is...",
"Hello @everyone is...": "Hej {'@'}alle er…",
"Webhook URL": "Webhook URL",
"wayToGetTeamsURL": "Du kan lære, hvordan du laver en webhook URL {0}.",
"Number": "Nummer",
@@ -239,7 +239,7 @@
"Application Token": "Program Token",
"Server URL": "Server URL",
"Priority": "Prioritet",
"Icon Emoji": "Icon Emoji",
"Icon Emoji": "Ikon Emoji",
"Channel Name": "Kanalnavn",
"Uptime Kuma URL": "Uptime Kuma URL",
"aboutWebhooks": "Mere info om Webhooks på: {0}",
@@ -271,8 +271,8 @@
"Read more": "Læs mere",
"appriseInstalled": "Apprise er installeret.",
"appriseNotInstalled": "Apprise er ikke installeret. {0}",
"Access Token": "Access Token",
"Channel access token": "kanaladgangstoken",
"Access Token": "Adgangsnøgle",
"Channel access token": "Kanal adgangsnøgle",
"Line Developers Console": "Line Udviklerkonsol",
"lineDevConsoleTo": "Line Udviklerkonsol - {0}",
"Basic Settings": "Basisindstillinger",
@@ -300,7 +300,7 @@
"PushUrl": "Push URL",
"HeadersInvalidFormat": "\"request headers\"-erne er ikke gyldige JSON: ",
"BodyInvalidFormat": "\"request body\"-en er ikke gyldige JSON: ",
"Monitor History": "Overvåger Historik",
"Monitor History": "Overvågningshistorik",
"clearDataOlderThan": "Gem overvågningshistorikdata i {0} dage.",
"PasswordsDoNotMatch": "Adgangskoderne stemmer ikke overens.",
"records": "forekomster",
@@ -337,7 +337,7 @@
"Show Tags": "Vis Etiketter",
"Hide Tags": "Skjul Etiketter",
"Description": "Beskrivelse",
"No monitors available.": "No monitors available.",
"No monitors available.": "Ingen tilgængelige overvågninger.",
"Add one": "Tilføj en",
"No Monitors": "Ingen Overvågere",
"Untitled Group": "Unavngivet Gruppe",
@@ -360,7 +360,7 @@
"From Name/Number": "Fra Navn/Nummer",
"Help": "Hjælp",
"Please use this option carefully!": "Brug venligst denne funktion med forsigtighed!",
"disableauth.message1": "Er du sikker på, at du vil <strong>deaktivere authentication</strong>?",
"disableauth.message1": "Er du sikker på, at du vil <strong>deaktivere autentifikation</strong>?",
"successMessage": "Succesmeddelelse",
"error": "fejl",
"critical": "kritisk",
@@ -386,15 +386,15 @@
"Notification Service": "Notifikationstjeneste",
"Domain": "Domæne",
"Google Analytics ID": "Google Analytics ID",
"Edit Tag": "Ændre Tag",
"Edit Tag": "Redigér etiket",
"Learn More": "Lær mere",
"Schedule maintenance": "Planlæg vedligeholdelse",
"Invalid": "Ugyldig",
"User": "Bruger",
"Installed": "Installeret",
"Not installed": "Ikke installeret",
"Running": "Køre",
"Not running": "Køre ikke",
"Running": "Kører",
"Not running": "Kører ikke",
"Remove Token": "Fjern Token",
"Start": "Start",
"Stop": "Stop",
@@ -412,7 +412,7 @@
"Trust Proxy": "Trust Proxy",
"For example: nginx, Apache and Traefik.": "For eksempel: nginx, Apache og Traefik.",
"Please read": "Læs venligst",
"Show Powered By": "Vis Powered By",
"Show Powered By": "Vis Drevet af",
"Domain Names": "Domænenavne",
"signedInDisp": "Logget ind som {0}",
"Certificate Expiry Notification": "Meddelelse om udløbsdato for certifikatet",
@@ -448,7 +448,7 @@
"loadingError": "Kan ikke hente dataene, prøv igen senere.",
"Custom": "Brugerdefineret",
"Monitor": "Overvåger | Overvågere",
"Specific Monitor Type": "Specifik monitor-type",
"Specific Monitor Type": "Specifik overvågningstype",
"topic": "Emne",
"Fingerprint:": "Fingerprint:",
"Issuer:": "Udsteder:",
@@ -472,23 +472,23 @@
"Display Timezone": "Vis tidszone",
"Server Timezone": "Serverens tidszone",
"IconUrl": "Ikon URL",
"Enable DNS Cache": "Aktiver DNS Cache",
"Enable": "Aktiver",
"Disable": "Deaktiver",
"Enable DNS Cache": "Aktivér DNS Cache",
"Enable": "Aktivér",
"Disable": "Deaktivér",
"dnsCacheDescription": "Det fungerer muligvis ikke i alle IPv6-miljøer, så deaktiver det, hvis du støder på problemer.",
"Maintenance Time Window of a Day": "Tidsvindue for vedligeholdelse af en dag",
"Schedule Maintenance": "Planlæg vedligeholdelse",
"Date and Time": "Dato og klokkeslæt",
"plugin": "Plugin | Plugins",
"install": "Installer",
"uninstall": "Afinstaller",
"install": "Installér",
"uninstall": "Afinstallér",
"uninstalling": "Afinstallerer",
"confirmUninstallPlugin": "Er du sikker på, at du vil afinstallere dette plugin?",
"installing": "Installerer",
"markdownSupported": "Markdown syntax understøttet",
"Affected Monitors": "Berørte monitors",
"Affected Monitors": "Berørte overvågninger",
"All Status Pages": "Alle statussider",
"Pick Affected Monitors...": "Vælg berørte monitors…",
"Pick Affected Monitors...": "Vælg berørte overvågninger…",
"Select status pages...": "Vælg statusside…",
"proxyDescription": "Proxyer skal være tilknyttet en monitor for at fungere.",
"Accept characters:": "Accepter tegn:",
@@ -581,5 +581,44 @@
"deleteAPIKeyMsg": "Er du sikker på du vil slette denne API nøgle?",
"pagertreeDoNothing": "Gør intet",
"Start of maintenance": "Start på vedligeholdelse",
"Add New Tag": "Tilføj nyt tag"
"Add New Tag": "Tilføj ny etiket",
"setupDatabaseChooseDatabase": "Hvilken database vil du gerne bruge?",
"Saved.": "Gemt.",
"authUserInactiveOrDeleted": "Denne bruger er inaktiv eller er blevet slettet.",
"webhookBodyPresetOption": "Forudindstilling - {0}",
"filterActive": "Aktiv",
"filterActivePaused": "På pause",
"Select": "Vælg",
"selectedMonitorCount": "Valgte: {0}",
"chromeExecutableAutoDetect": "Autodetektér",
"pushyToken": "Enhedsnøgle",
"You can divide numbers with": "Du kan dividere numre med",
"alertaEnvironment": "Miljø",
"promosmsAllowLongSMS": "Tillad lang SMS",
"smseagleToken": "API Adgangsnøgle",
"twilioFromNumber": "Fra nummer",
"twilioToNumber": "Til nummer",
"twilioApiKey": "API nøgle (valgfrit)",
"ntfyUsernameAndPassword": "Brugernavn og adgangskode",
"lunaseaUserID": "Bruger ID",
"lunaseaDeviceID": "Enheds ID",
"lunaseaTarget": "Mål",
"Expiry": "Udløber",
"pushDeerServerDescription": "Efterlad blank for at bruge den officielle server",
"Close": "Luk",
"Group": "Gruppe",
"Device Token": "Enhedsnøgle",
"dbName": "Database navn",
"Monitor Group": "Overvågningsgruppe",
"telegramSendSilently": "Send lydløst",
"smseagleRecipientType": "Modtager type",
"smseagleRecipient": "Modtager(e) (Adskil med komma)",
"statusPageRefreshIn": "Genopfrisk om: {0}",
"Home": "Hjem",
"tagNotFound": "Etiket blev ikke fundet.",
"pushOthers": "Andre",
"confirmDeleteTagMsg": "Er du sikker på at du vil slette denne etiket? Overvågninger med denne etiket vil ikke blive slettet.",
"resendEveryXTimes": "Gensend hver {0} gang",
"resendDisabled": "Gensendelse deaktiveret",
"Reconnecting...": "Genforbinder..."
}

View File

@@ -658,7 +658,7 @@
"webhookAdditionalHeadersDesc": "Legt zusätzliche Kopfzeilen fest, die mit dem Webhook gesendet werden. Jede Kopfzeile sollte als JSON Schlüssel/Wert definiert werden.",
"Packet Size": "Paketgrösse",
"IconUrl": "Symbol URL",
"Enable DNS Cache": "DNS-Cache für HTTP(s)-Monitore aktivieren",
"Enable DNS Cache": "(Veraltet) DNS-Cache für HTTP(s)-Monitore aktivieren",
"Help": "Hilfe",
"Game": "Spiel",
"General Monitor Type": "Allgemeiner Monitortyp",
@@ -824,7 +824,7 @@
"nostrRelays": "Nostr relays",
"nostrRelaysHelp": "Eine Relay-URL pro Zeile",
"nostrRecipients": "Öffentliche Schlüssel des Empfängers (npub)",
"gamedigGuessPort": "Gamedig: Guess Port",
"gamedigGuessPort": "Gamedig: Vermuteter Port",
"Request Timeout": "Zeitüberschreitung der Anfrage",
"styleElapsedTimeShowNoLine": "Anzeigen (keine Zeile)",
"styleElapsedTimeShowWithLine": "Anzeigen (mit Zeile)",
@@ -835,5 +835,37 @@
"gamedigGuessPortDescription": "Der vom Valve Server Query Protocol verwendete Port kann sich vom Port des Clients unterscheiden. Versuche dies, wenn der Monitor keine Verbindung zum Server herstellen kann.",
"timeoutAfter": "Zeitüberschreitung nach {0} Sekunden",
"styleElapsedTime": "Verstrichene Zeit unter der Prüfintervallleiste",
"Check/Uncheck": "Aktivieren/Deaktivieren"
"Check/Uncheck": "Aktivieren/Deaktivieren",
"enableNSCD": "Aktiviere NSCD (Name Service Cache Daemon) zur Zwischenspeicherung aller DNS-Anfragen",
"setupDatabaseChooseDatabase": "Welche Datenbank möchtest du verwenden?",
"setupDatabaseEmbeddedMariaDB": "Du brauchst nichts einzustellen. Dieses Docker-Image hat automatisch eine MariaDB für dich eingerichtet und konfiguriert. Uptime Kuma wird sich mit dieser Datenbank über einen Unix-Socket verbinden.",
"dbName": "Datenbank Name",
"setupDatabaseMariaDB": "Mit externer MariaDB-Datenbank verbinden. Du musst die Verbindungsinformationen für die Datenbank festlegen.",
"setupDatabaseSQLite": "Eine einfache Datenbankdatei, empfohlen für kleinere Bereitstellungen. Vor v2.0.0 verwendete Uptime Kuma SQLite als Standarddatenbank.",
"Saved.": "Gespeichert.",
"monitorToastMessagesLabel": "Toast-Benachrichtigungen überwachen",
"toastSuccessTimeout": "Zeitüberschreitung für Erfolgsbenachrichtigungen",
"toastErrorTimeout": "Zeitüberschreitung für Fehlerbenachrichtigungen",
"monitorToastMessagesDescription": "Toast-Benachrichtigungen für Monitore verschwinden nach einer bestimmten Zeit in Sekunden. Auf -1 setzen, um die Zeitüberschreitung zu deaktivieren. Der Wert 0 deaktiviert die Toast-Benachrichtigungen.",
"Bark API Version": "Bark API Version",
"pushViewCode": "Wie verwendet man den Push-Monitor? (Code anzeigen)",
"pushOthers": "Sonstige",
"programmingLanguages": "Programmiersprachen",
"authInvalidToken": "Ungültiges Token.",
"authIncorrectCreds": "Falscher Benutzername oder falsches Passwort.",
"2faAlreadyEnabled": "2FA ist bereits aktiviert.",
"2faEnabled": "2FA ist aktiviert.",
"2faDisabled": "2FA ist deaktiviert.",
"successResumed": "Erfolgreich wiederaufgenommen.",
"successPaused": "Erfolgreich pausiert.",
"successDeleted": "Erfolgreich gelöscht.",
"successEdited": "Erfolgreich bearbeitet.",
"successBackupRestored": "Sicherung erfolgreich wiederhergestellt.",
"successEnabled": "Erfolgreich aktiviert.",
"tagNotFound": "Tag nicht gefunden.",
"foundChromiumVersion": "Gefunden Chromium/Chrome. Version: {0}",
"authUserInactiveOrDeleted": "Der Benutzer ist inaktiv oder gelöscht.",
"successAdded": "Erfolgreich hinzugefügt.",
"successAuthChangePassword": "Das Passwort wurde erfolgreich aktualisiert.",
"successDisabled": "Erfolgreich deaktiviert."
}

View File

@@ -1,5 +1,5 @@
{
"languageName": "Deutsch (Deutschland)",
"languageName": "Englisch",
"Settings": "Einstellungen",
"Dashboard": "Dashboard",
"New Update": "Aktualisierung verfügbar",
@@ -253,7 +253,7 @@
"Uptime Kuma URL": "Uptime Kuma URL",
"aboutWebhooks": "Weitere Informationen zu Webhooks auf: {0}",
"aboutChannelName": "Gebe den Kanalnamen ein in {0} Feld Kanalname, falls du den Webhook-Kanal umgehen möchtest. Ex: #other-channel",
"aboutKumaURL": "Wenn das Feld für die Uptime Kuma URL leer gelassen wird, wird standardmäßig die GitHub Projekt Seite verwendet.",
"aboutKumaURL": "Wenn Sie das Feld für die Uptime Kuma URL leer lassen, wird es standardmäßig auf die Projekt-GitHub-Seite gesetzt.",
"emojiCheatSheet": "Emoji Cheat Sheet: {0}",
"User Key": "Benutzerschlüssel",
"Device": "Gerät",
@@ -600,7 +600,7 @@
"Optional": "Optional",
"squadcast": "Squadcast",
"SendKey": "SendKey",
"SMSManager API Docs": "SMSManager API Dokumente",
"SMSManager API Docs": "SMSManager API Dokumente ",
"Gateway Type": "Gateway Typ",
"SMSManager": "SMSManager",
"You can divide numbers with": "Du kannst Zahlen teilen mit",
@@ -644,7 +644,7 @@
"Help": "Hilfe",
"Game": "Spiel",
"Custom": "Benutzerdefiniert",
"Enable DNS Cache": "DNS-Cache für HTTP(s)-Monitore aktivieren",
"Enable DNS Cache": "(Veraltet) Aktivieren Sie den DNS-Cache für HTTP(s)-Überwachungen",
"Enable": "Aktivieren",
"Disable": "Deaktivieren",
"Custom Monitor Type": "Benutzerdefinierter Monitortyp",
@@ -821,7 +821,7 @@
"pushDeerServerDescription": "Leer lassen um den offiziellen Server zu verwenden",
"FlashDuty Severity": "Schweregrad",
"nostrRelays": "Nostr relays",
"gamedigGuessPort": "Gamedig: Guess Port",
"gamedigGuessPort": "Gamedig: Vermuteter Port",
"Request Timeout": "Zeitüberschreitung der Anfrage",
"styleElapsedTimeShowNoLine": "Anzeigen (keine Zeile)",
"Select": "Auswählen",
@@ -838,5 +838,37 @@
"nostrRecipients": "Öffentliche Schlüssel des Empfängers (npub)",
"nostrRecipientsHelp": "npub-Format, eine pro Zeile",
"showCertificateExpiry": "Ablauf des Zertifikats anzeigen",
"noOrBadCertificate": "Kein/schlechtes Zertifikat"
"noOrBadCertificate": "Kein/schlechtes Zertifikat",
"enableNSCD": "Aktivieren Sie NSCD (Name Service Cache Daemon) zur Zwischenspeicherung aller DNS-Anfragen",
"setupDatabaseChooseDatabase": "Welche Datenbank möchtest du verwenden?",
"setupDatabaseEmbeddedMariaDB": "Du brauchst nichts einzustellen. Dieses Docker-Image hat automatisch eine MariaDB für dich eingerichtet und konfiguriert. Uptime Kuma wird sich mit dieser Datenbank über einen Unix-Socket verbinden.",
"dbName": "Datenbank Name",
"setupDatabaseMariaDB": "Mit externer MariaDB-Datenbank verbinden. Du musst die Verbindungsinformationen für die Datenbank festlegen.",
"setupDatabaseSQLite": "Eine einfache Datenbankdatei, empfohlen für kleinere Bereitstellungen. Vor v2.0.0 verwendete Uptime Kuma SQLite als Standarddatenbank.",
"Saved.": "Gespeichert.",
"toastSuccessTimeout": "Zeitüberschreitung für Erfolgsbenachrichtigungen",
"toastErrorTimeout": "Zeitüberschreitung für Fehlerbenachrichtigungen",
"monitorToastMessagesLabel": "Toast-Benachrichtigungen überwachen",
"monitorToastMessagesDescription": "Toast-Benachrichtigungen für Monitore verschwinden nach einer bestimmten Zeit in Sekunden. Auf -1 setzen, um die Zeitüberschreitung zu deaktivieren. Der Wert 0 deaktiviert die Toast-Benachrichtigungen.",
"Bark API Version": "Bark API Version",
"pushViewCode": "Wie verwendet man den Push-Monitor? (Code anzeigen)",
"pushOthers": "Sonstige",
"programmingLanguages": "Programmiersprachen",
"authInvalidToken": "Ungültiges Token.",
"authIncorrectCreds": "Falscher Benutzername oder falsches Passwort.",
"2faAlreadyEnabled": "2FA ist bereits aktiviert.",
"2faEnabled": "2FA ist aktiviert.",
"2faDisabled": "2FA ist deaktiviert.",
"successResumed": "Erfolgreich wiederaufgenommen.",
"successPaused": "Erfolgreich pausiert.",
"successDeleted": "Erfolgreich gelöscht.",
"successEdited": "Erfolgreich bearbeitet.",
"successBackupRestored": "Sicherung erfolgreich wiederhergestellt.",
"successDisabled": "Erfolgreich deaktiviert.",
"successEnabled": "Erfolgreich aktiviert.",
"tagNotFound": "Tag nicht gefunden.",
"authUserInactiveOrDeleted": "Der Benutzer ist inaktiv oder gelöscht.",
"successAdded": "Erfolgreich hinzugefügt.",
"successAuthChangePassword": "Das Passwort wurde erfolgreich aktualisiert.",
"foundChromiumVersion": "Gefunden Chromium/Chrome. Version: {0}"
}

View File

@@ -1,5 +1,10 @@
{
"languageName": "English",
"setupDatabaseChooseDatabase": "Which database do you want to use?",
"setupDatabaseEmbeddedMariaDB": "You don't need to set anything. This docker image have embedded and configured a MariaDB for you automatically. Uptime Kuma will connect to this database via unix socket.",
"setupDatabaseMariaDB": "Connect to an external MariaDB database. You need to set the database connection information.",
"setupDatabaseSQLite": "A simple database file, recommended for small-scale deployments. Prior to v2.0.0, Uptime Kuma used SQLite as the default database.",
"dbName": "Database Name",
"Settings": "Settings",
"Dashboard": "Dashboard",
"Help": "Help",
@@ -79,6 +84,9 @@
"Push URL": "Push URL",
"needPushEvery": "You should call this URL every {0} seconds.",
"pushOptionalParams": "Optional parameters: {0}",
"pushViewCode": "How to use Push monitor? (View Code)",
"pushOthers": "Others",
"programmingLanguages": "Programming Languages",
"Save": "Save",
"Notifications": "Notifications",
"Not available, please setup.": "Not available, please setup.",
@@ -205,7 +213,12 @@
"Content Type": "Content Type",
"webhookJsonDesc": "{0} is good for any modern HTTP servers such as Express.js",
"webhookFormDataDesc": "{multipart} is good for PHP. The JSON will need to be parsed with {decodeFunction}",
"webhookCustomBodyDesc": "Define a custom HTTP Body for the request. Template variables {msg}, {heartbeat}, {monitor} are accepted.",
"liquidIntroduction": "Templatability is achieved via the Liquid templating language. Please refer to the {0} for usage instructions. These are the available variables:",
"templateMsg": "message of the notification",
"templateHeartbeatJSON": "object describing the heartbeat",
"templateMonitorJSON": "object describing the monitor",
"templateLimitedToUpDownCertNotifications": "only available for UP/DOWN/Certificate expiry notifications",
"templateLimitedToUpDownNotifications": "only available for UP/DOWN notifications",
"webhookAdditionalHeadersTitle": "Additional Headers",
"webhookAdditionalHeadersDesc": "Sets additional headers sent with the webhook. Each header should be defined as a JSON key/value.",
"webhookBodyPresetOption": "Preset - {0}",
@@ -236,6 +249,7 @@
"successMessage": "Success Message",
"successMessageExplanation": "MQTT message that will be considered as success",
"recent": "Recent",
"Reset Token": "Reset Token",
"Done": "Done",
"Info": "Info",
"Security": "Security",
@@ -482,7 +496,19 @@
"secureOptionTLS": "TLS (465)",
"Ignore TLS Error": "Ignore TLS Error",
"From Email": "From Email",
"emailCustomisableContent": "Customisable content",
"smtpLiquidIntroduction": "The following two fields are templatable via the Liquid templating Language. Please refer to the {0} for usage instructions. These are the available variables:",
"emailCustomSubject": "Custom Subject",
"leave blank for default subject": "leave blank for default subject",
"emailCustomBody": "Custom Body",
"leave blank for default body": "leave blank for default body",
"emailTemplateServiceName": "Service Name",
"emailTemplateHostnameOrURL": "Hostname or URL",
"emailTemplateStatus": "Status",
"emailTemplateMonitorJSON": "object describing the monitor",
"emailTemplateHeartbeatJSON": "object describing the heartbeat",
"emailTemplateMsg": "message of the notification",
"emailTemplateLimitedToUpDownNotification": "only available for UP/DOWN heartbeats, otherwise null",
"To Email": "To Email",
"smtpCC": "CC",
"smtpBCC": "BCC",
@@ -623,6 +649,7 @@
"TemplateCode": "TemplateCode",
"SignName": "SignName",
"Sms template must contain parameters: ": "Sms template must contain parameters: ",
"Bark API Version": "Bark API Version",
"Bark Endpoint": "Bark Endpoint",
"Bark Group": "Bark Group",
"Bark Sound": "Bark Sound",
@@ -782,6 +809,10 @@
"Badge URL": "Badge URL",
"Group": "Group",
"Monitor Group": "Monitor Group",
"monitorToastMessagesLabel": "Monitor Toast notifications",
"monitorToastMessagesDescription": "Toast notifications for monitors disappear after given time in seconds. Set to -1 disables timeout. Set to 0 disables toast notifications.",
"toastErrorTimeout": "Timeout for Error Notifications",
"toastSuccessTimeout": "Timeout for Success Notifications",
"Kafka Brokers": "Kafka Brokers",
"Enter the list of brokers": "Enter the list of brokers",
"Press Enter to add broker": "Press Enter to add broker",
@@ -799,8 +830,8 @@
"noGroupMonitorMsg": "Not Available. Create a Group Monitor First.",
"Close": "Close",
"Request Body": "Request Body",
"wayToGetFlashDutyKey":"You can go to Channel -> (Select a Channel) -> Integrations -> Add a new integration' page, add a 'Custom Event' to get a push address, copy the Integration Key in the address. For more information, please visit",
"FlashDuty Severity":"Severity",
"wayToGetFlashDutyKey": "You can go to Channel -> (Select a Channel) -> Integrations -> Add a new integration' page, add a 'Custom Event' to get a push address, copy the Integration Key in the address. For more information, please visit",
"FlashDuty Severity": "Severity",
"nostrRelays": "Nostr relays",
"nostrRelaysHelp": "One relay URL per line",
"nostrSender": "Sender Private Key (nsec)",
@@ -809,5 +840,24 @@
"showCertificateExpiry": "Show Certificate Expiry",
"noOrBadCertificate": "No/Bad Certificate",
"gamedigGuessPort": "Gamedig: Guess Port",
"gamedigGuessPortDescription": "The port used by Valve Server Query Protocol may be different from the client port. Try this if the monitor cannot connect to your server."
"gamedigGuessPortDescription": "The port used by Valve Server Query Protocol may be different from the client port. Try this if the monitor cannot connect to your server.",
"Saved.": "Saved.",
"authUserInactiveOrDeleted": "The user is inactive or deleted.",
"authInvalidToken": "Invalid Token.",
"authIncorrectCreds": "Incorrect username or password.",
"2faAlreadyEnabled": "2FA is already enabled.",
"2faEnabled": "2FA Enabled.",
"2faDisabled": "2FA Disabled.",
"successAdded": "Added Successfully.",
"successResumed": "Resumed Successfully.",
"successPaused": "Paused Successfully.",
"successDeleted": "Deleted Successfully.",
"successEdited": "Edited Successfully.",
"successAuthChangePassword": "Password has been updated successfully.",
"successBackupRestored": "Backup successfully restored.",
"successDisabled": "Disabled Successfully.",
"successEnabled": "Enabled Successfully.",
"tagNotFound": "Tag not found.",
"foundChromiumVersion": "Found Chromium/Chrome. Version: {0}",
"GrafanaOncallUrl": "Grafana Oncall URL"
}

View File

@@ -270,7 +270,7 @@
"Display Timezone": "Mostrar Zona Horaria",
"Server Timezone": "Servidor de Zona Horaria",
"statusPageMaintenanceEndDate": "Finaliza",
"Enable DNS Cache": "Habilitar Cache DNS de monitores HTTP(s)",
"Enable DNS Cache": "(Obsoleto) Habilitar caché DNS para monitores HTTP(s)",
"No Maintenance": "Sin Mantenimiento",
"weekdayShortSun": "Dom",
"dayOfWeek": "Día de la Semana",
@@ -556,11 +556,11 @@
"affectedMonitorsDescription": "Selecciona los monitores que se ven afectados por el mantenimiento actual",
"affectedStatusPages": "Muestra este mensaje de mantenimiento en las páginas de estado seleccionadas",
"atLeastOneMonitor": "Selecciona al menos un monitor afectado",
"endpoint": "punto final",
"endpoint": "endpoint",
"promosmsPassword": "Contraseña API",
"pushoversounds pushover": "Pushover (predeterminado)",
"pushoversounds bike": "Bicicleta",
"pushoversounds bugle": "Bugle",
"pushoversounds bugle": "Trompeta",
"pushoversounds cashregister": "Caja Registradora",
"pushoversounds classical": "Clásica",
"pushoversounds cosmic": "Cósmico",
@@ -653,18 +653,18 @@
"gorush": "Gorush",
"squadcast": "Squadcast",
"Maintenance Time Window of a Day": "Ventana de tiempo de mantenimiento de un día",
"Effective Date Range": "Rango de Fecha Efectivo (Opcional)",
"Effective Date Range": "Rango de Fecha Vigente (Opcional)",
"Free Mobile User Identifier": "Identificador de Usuario de Free Mobile",
"Gateway Type": "Tipo de Puerta de Enlace",
"Gateway Type": "Tipo de puerta de enlace",
"SMSManager": "SMSManager",
"goAlertInfo": "GoAlert es una aplicación de código abierto para la programación de guardias, escaladas automatizadas y notificaciones (como SMS o llamadas de voz). ¡Involucre automáticamente a la persona adecuada, de la manera correcta y en el momento adecuado! {0}",
"Free Mobile API Key": "Clave API de Free Mobile",
"high": "alto",
"SMSManager API Docs": "Documentación API de SMSManager ",
"smseagleContact": "Nombre(s) de contacto de Guía Telefónica",
"smseagleContact": "Nombre(s) de contacto en la guía telefónica",
"smseagleToken": "Token de Acceso a la API",
"smseagleUrl": "URL del dispositivo SMSEagle",
"Legacy Octopush-DM": "Octopush-DM heredado",
"Legacy Octopush-DM": "Octopush-DM (legacy)",
"HomeAssistant": "Home Assistant",
"goAlertIntegrationKeyInfo": "Obtenga la clave de integración API genérica para el servicio en este formato \"aaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\", generalmente el valor del parámetro token de la URL copiada.",
"Topic": "Tema",
@@ -676,23 +676,23 @@
"alertaRecoverState": "Estado de Recuperación",
"serwersms": "SerwerSMS.pl",
"serwersmsAPIUser": "Nombre de usuario de API (inc. webapi_ prefix)",
"smseagleGroup": "Nombre(s) de grupo de Guía Telefónica",
"smseagleGroup": "Nombre/s del grupo de la guía telefónica",
"Unpin": "Dejar de Fijar",
"Prefix Custom Message": "Prefijo personalizado",
"markdownSupported": "Sintaxis de Markdown soportada",
"Server Address": "Dirección del Servidor",
"Learn More": "Aprende Más",
"Pick a RR-Type...": "Seleccione un Tipo RR",
"Pick a RR-Type...": "Seleccione un Tipo RR",
"onebotHttpAddress": "Dirección HTTP OneBot",
"SendKey": "Clave de Envío",
"octopushAPIKey": "\"Clave API\" desde credenciales API HTTP en panel de control",
"octopushLogin": "\"Inicio de Sesión\" a partir de las credenciales API HTTP en el panel de control",
"octopushLogin": "\"Inicio de sesión\" desde credenciales API HTTP en panel de control",
"ntfy Topic": "Tema ntfy",
"Google Analytics ID": "ID Analíticas de Google",
"Edit Tag": "Editar Etiqueta",
"SignName": "Firma",
"Bark Endpoint": "Endpoint Bark",
"WebHookUrl": "WebHookUrl",
"WebHookUrl": "URL del WebHook",
"High": "Alto",
"alertaApiEndpoint": "Endpoint API",
"Body Encoding": "Codificación del cuerpo",
@@ -700,7 +700,7 @@
"Expiry": "Expiración",
"API Keys": "Claves API",
"Key Added": "Clave añadida",
"Add Another": "Añadir otro",
"Add Another": "Agregar otro/a",
"Continue": "Continuar",
"Don't expire": "No caduca",
"apiKey-inactive": "Inactivo",
@@ -713,7 +713,7 @@
"cloneOf": "Clon de {0}",
"pagertreeDoNothing": "No hacer nada",
"pagertreeResolve": "Resolución automática",
"pagertreeCritical": "Crítico",
"pagertreeCritical": "Crítico/a",
"pagertreeHigh": "Alto",
"pagertreeMedium": "Medio",
"pagertreeLow": "Bajo",
@@ -728,7 +728,7 @@
"telegramMessageThreadIDDescription": "Opcional Identificador único para el hilo de mensajes de destino (asunto) del foro; solo para supergrupos de foros",
"telegramProtectContent": "Proteger Forwarding/Saving",
"telegramProtectContentDescription": "Si se activa, los mensajes del bot en Telegram estarán protegidos contra el reenvío y el guardado.",
"notificationRegional": "Regional",
"notificationRegional": "Regionales",
"Clone Monitor": "Clonar Monitor",
"telegramSendSilently": "Enviar en silencio",
"telegramSendSilentlyDescription": "Envía el mensaje en silencio. Los usuarios recibirán una notificación sin sonido.",
@@ -747,7 +747,7 @@
"cronSchedule": "Cronograma: ",
"invalidCronExpression": "Expresión Cron invalida:{0}",
"statusPageRefreshIn": "Reinicio en: {0}",
"twilioAuthToken": "Token de Autentificación",
"twilioAuthToken": "Token de autenticación / Secreto de clave API",
"ntfyUsernameAndPassword": "Nombre de Usuario y Contraseña",
"ntfyAuthenticationMethod": "Método de Autenticación",
"Cannot connect to the socket server": "No se puede conectar al servidor socket",
@@ -766,5 +766,88 @@
"filterActivePaused": "Pausado",
"Home": "Inicio",
"Expected Value": "Valor esperado",
"Json Query": "Consulta Json"
"Json Query": "Consulta Json",
"invertKeywordDescription": "Comprobar si la palabra clave está ausente en vez de presente.",
"enableNSCD": "Habilitar NSCD (Demonio de Caché de Servicio de Nombres) para almacenar en caché todas las solicitudes DNS",
"jsonQueryDescription": "Realiza una consulta JSON contra la respuesta y verifica el valor esperado (el valor de retorno se convertirá a una cadena para la comparación). Consulta <a href='https://jsonata.org/'>jsonata.org</a> para obtener documentación sobre el lenguaje de consulta. Puede encontrar un espacio de prueba <a href='https://try.jsonata.org/'>aquí</a>.",
"Request Timeout": "Tiempo de espera máximo de petición",
"timeoutAfter": "Expirar después de {0} segundos",
"chromeExecutableDescription": "Para usuarios de Docker, si Chromium no está instalado, puede que tarde unos minutos en ser instalado y mostrar el resultado de la prueba. Usa 1GB de espacio.",
"chromeExecutable": "Ejecutable de Chrome/Chromium",
"Monitor Setting": "Ajustes del monitor de {0}",
"Show Clickable Link": "Mostrar enlace clickeable",
"Open Badge Generator": "Abrir generador de insignias",
"Badge Generator": "Generador de insignias de {0}",
"Badge Type": "Tipo de insignia",
"Badge Label": "Etiqueta de la insignia",
"Badge Label Color": "Color de la etiqueta de la insignia",
"Badge Color": "Color de la insignia",
"Badge Label Prefix": "Prefijo de la etiqueta de insignia",
"Badge Preview": "Vista previa de la insignia",
"Badge Up Color": "Color de la insignia superior",
"Badge Down Color": "Color de la insignia inferior",
"Badge Pending Color": "Color de la insignia pendiente",
"Badge Maintenance Color": "Color de mantenimiento de la insignia",
"Badge Warn Days": "Días de advertencia de insignia",
"Badge Down Days": "Días sin insignia",
"Badge Style": "Estilo de insignia",
"Badge URL": "URL de la insignia",
"Group": "Grupo",
"Monitor Group": "Grupo de Monitoreo",
"Kafka Brokers": "Brokers de Kafka",
"Enter the list of brokers": "Ingrese a la lista de brokers",
"Press Enter to add broker": "Presione Enter para agregar un broker",
"Kafka Topic Name": "Nombre del tema Kafka",
"Kafka Producer Message": "Mensaje del Productor de Kafka",
"Enable Kafka SSL": "Habilitar Kafka SSL",
"Kafka SASL Options": "Opciones de Kafka SASL",
"Mechanism": "Mecanismo",
"Pick a SASL Mechanism...": "Elija un mecanismo SASL...",
"Authorization Identity": "Identidad de autorización",
"AccessKey Id": "ID de clave de acceso",
"Secret AccessKey": "Secreto de la clave de acceso",
"Session Token": "Token de sesión",
"Close": "Cerrar",
"Request Body": "Cuerpo de solicitud",
"FlashDuty Severity": "Gravedad",
"nostrRelays": "Relays de Nostr",
"nostrRelaysHelp": "Una URL de retransmisión por línea",
"nostrSender": "Clave Privada del remitente (nsec)",
"nostrRecipients": "Claves públicas de destinatarios (npub)",
"nostrRecipientsHelp": "formato npub, uno por línea",
"showCertificateExpiry": "Mostrar caducidad del certificado",
"noOrBadCertificate": "Certificado Nulo/Incorrecto",
"aboutNotifyChannel": "Notificar canal activará una notificación de escritorio o móvil para todos los miembros del canal, ya sea que su disponibilidad esté activa o ausente.",
"Server URL should not contain the nfty topic": "La URL del servidor no debe contener el tema nfty",
"PushDeer Server": "Servidor PushDeer",
"pushDeerServerDescription": "Dejar en blanco para usar el servidor oficial",
"Badge Duration (in hours)": "Duración de la insignia (en horas)",
"Badge Prefix": "Prefijo del valor de la insignia",
"Badge Suffix": "Sufijo del valor de la insignia",
"Badge Label Suffix": "Sufijo de la etiqueta de insignia",
"Badge Warn Color": "Color de advertencia de insignia",
"Badge value (For Testing only.)": "Valor de la insignia (Solo para pruebas.)",
"Enable Kafka Producer Auto Topic Creation": "Habilitar la Creación Automática de Temas del Productor de Kafka",
"noGroupMonitorMsg": "No disponible. Cree primero un monitor de grupo.",
"wayToGetFlashDutyKey": "Puede ir a Canal -> (Seleccionar un canal) -> Integraciones -> Agregar una nueva página de integración, agregar un 'Evento personalizado' para obtener una dirección push, copiar la clave de integración en la dirección. Para mayor información por favor visite",
"gamedigGuessPort": "Gamedig: Adivinar el puerto",
"gamedigGuessPortDescription": "El puerto utilizado por Valve Server Query Protocol puede ser diferente del puerto del cliente. Pruebe esto si el monitor no puede conectarse a su servidor.",
"twilioApiKey": "Clave de la API (opcional)",
"styleElapsedTime": "Tiempo transcurrido en la barra de latidos",
"styleElapsedTimeShowNoLine": "Mostrar (sin línea)",
"styleElapsedTimeShowWithLine": "Mostrar (Con línea)",
"webhookCustomBodyDesc": "Define un cuerpo HTTP personalizado para la petición. Las variables que puedes usar como plantillas son {msg}, {heartbeat}, y {monitor}.",
"webhookBodyPresetOption": "Preajuste- {0}",
"tailscalePingWarning": "Para utilizar el monitor Tailscale Ping, debe instalar Uptime Kuma sin Docker y también instalar el cliente Tailscale en su servidor.",
"Bark API Version": "Versión de la API Bark",
"monitorToastMessagesDescription": "Las notificaciones Toast para monitores desaparecen después de un tiempo dado en segundos. Establecer a -1 desactiva el tiempo de espera. Si se establece en 0, se desactivan las notificaciones.",
"Saved.": "Guardado.",
"monitorToastMessagesLabel": "Monitorizar las notificaciones Toast",
"toastSuccessTimeout": "Tiempo de espera para notificaciones de éxito",
"toastErrorTimeout": "Tiempo de espera para notificaciones de error",
"setupDatabaseChooseDatabase": "¿Qué base de datos desea utilizar?",
"setupDatabaseEmbeddedMariaDB": "No necesitas configurar nada. Esta imagen docker ha incrustado y configurado un MariaDB para ti automáticamente. Uptime Kuma se conectará a esta base de datos a través de un socket unix.",
"setupDatabaseMariaDB": "Conectarse a una base de datos MariaDB externa. Debe configurar la información de conexión a la base de datos.",
"setupDatabaseSQLite": "Un archivo de base de datos simple, recomendado para despliegues a pequeña escala. Antes de la versión 2.0.0, Uptime Kuma utilizaba SQLite como base de datos predeterminada.",
"dbName": "Nombre de la base de datos"
}

View File

@@ -672,7 +672,7 @@
"Server Timezone": "منطقه زمانی در سرور",
"statusPageMaintenanceEndDate": "پایان",
"IconUrl": "URL آیکون",
"Enable DNS Cache": "فعال سازی کش DNS برای مانیتور های HTTP",
"Enable DNS Cache": "(منسوخ شده) فعال سازی کش DNS برای مانیتور های HTTP",
"Access Token": "توکن دسترسی",
"smtp": "ایمیل (SMTP)",
"Device": "دستگاه",
@@ -808,5 +808,37 @@
"noOrBadCertificate": "بدون سرتیفیکت یا بد",
"Invert Keyword": "کلمه کلیدی معکوس",
"Expected Value": "مقدار مورد انتظار",
"Json Query": "کوئری جیسون"
"Json Query": "کوئری جیسون",
"Saved.": "ذخیره شده.",
"setupDatabaseChooseDatabase": "از چه دیتابیسی میخواهید استفاده کنید؟",
"setupDatabaseEmbeddedMariaDB": "شما نیازی نیست چیزی را تنظیم کنید . این Image داکر یک MariaDB را به طور خودکار برای شما جاسازی و پیکربندی کرده است. آپتایم کوما از طریق سوکت یونیکس به این دیتابیس متصل می شود.",
"setupDatabaseSQLite": "یک فایل دیتابیس ساده که برای استقرار در مقیاس کوچک توصیه می شود. قبل از نسخه 2.0.0، Uptime Kuma از SQLite به عنوان دیتابیس پیش فرض استفاده می کرد.",
"enableNSCD": "فعال سازی NSCD (Name Service Cache Daemon) برای کش کردن تمام ریکوئست های DNS",
"setupDatabaseMariaDB": "به یک دیتابیس خارجی MariaDB متصل شوید. شما باید اطلاعات اتصال دیتابیس را تنظیم کنید.",
"dbName": "نام دیتابیس",
"Bark API Version": "نسخه API Bark",
"monitorToastMessagesDescription": "پیام های Toasy برای مانیتورها پس از زمان معین در چند ثانیه ناپدید می‌شوند. با تنظیم آن بر روی -1 باعث غیرفعال شدن مهلت زمانی می شود. تنظیم بر روی 0 پیام های Toastرا غیرفعال می‌کند.",
"monitorToastMessagesLabel": "پیام های Monitor Toast",
"toastErrorTimeout": "تایم اوت برای پیام های خطا",
"toastSuccessTimeout": "تایم اوت برای پیام های موفقیت آمیز",
"authInvalidToken": "توکن نامعتبر است.",
"authIncorrectCreds": "نام کاربری یا رمز عبور اشتباه است.",
"2faEnabled": "تایید دو مرحله ای فعال است.",
"2faDisabled": "تایید دو مرحله ای غیرفعال است.",
"successPaused": "با موفقیت متوقف شد.",
"successDeleted": "با موفقیت حذف شد.",
"successEdited": "با موفقیت ویرایش شد.",
"successBackupRestored": "بکاپ با موفقیت بازیابی شد.",
"successDisabled": "با موفقیت غیرفعال شد.",
"successEnabled": "با موفقیت فعال شد.",
"tagNotFound": "برچسب پیدا نشد.",
"foundChromiumVersion": "کرومیوم/کروم پیدا شد. نسخه: {0}",
"pushViewCode": "چگونه از پوش مانیتور استفاده شود؟ (نگاه به کد)",
"programmingLanguages": "زبان های برنامه نویسی",
"authUserInactiveOrDeleted": "کاربر غیرفعال یا حذف شده است.",
"2faAlreadyEnabled": "تایید دومرحله ای قبلاً فعال شده است.",
"successAdded": "با موفقیت اضافه شد.",
"successResumed": "با موفقیت از سر گرفته شد.",
"successAuthChangePassword": "رمز عبور با موفقیت به روز رسانی شد.",
"pushOthers": "غیره"
}

View File

@@ -650,7 +650,7 @@
"Server Timezone": "Fuseau horaire du serveur",
"statusPageMaintenanceEndDate": "Fin",
"IconUrl": "URL vers l'icône",
"Enable DNS Cache": "Activer le cache DNS pour les sondes HTTP(s)",
"Enable DNS Cache": "(Obsolète) Activer le cache DNS pour les sondes HTTP(s)",
"Enable": "Activer",
"Disable": "Désactiver",
"dnsCacheDescription": "Il peut ne pas fonctionner dans certains environnements IPv6, désactivez-le si vous rencontrez des problèmes.",
@@ -677,7 +677,7 @@
"infiniteRetention": "Définissez la valeur à 0 pour une durée de conservation infinie.",
"Monitor": "Sonde | Sondes",
"Custom": "Personnalisé",
"confirmDeleteTagMsg": "Voulez-vous vraiment supprimer cette étiquettes ? Les moniteurs associés ne seront pas supprimés.",
"confirmDeleteTagMsg": "Voulez-vous vraiment supprimer cette étiquette ? Les sondes associées ne seront pas supprimées.",
"promosmsAllowLongSMS": "Autoriser les longs SMS",
"Help": "Aide",
"Game": "Jeux",
@@ -752,7 +752,7 @@
"ntfyAuthenticationMethod": "Méthode d'authentification",
"pushoverMessageTtl": "TTL Message (Secondes)",
"Show Clickable Link": "Afficher le lien cliquable",
"Show Clickable Link Description": "Si cette case est cochée, tous ceux qui ont accès à cette page d'état peuvent accéder à l'URL du moniteur.",
"Show Clickable Link Description": "Si cette case est cochée, tous ceux qui ont accès à cette page d'état peuvent accéder à l'URL de la sonde.",
"Open Badge Generator": "Ouvrir le générateur de badges",
"Badge Type": "Type de badge",
"Badge Duration": "Durée du badge",
@@ -834,9 +834,41 @@
"wayToGetFlashDutyKey": "Vous pouvez aller dans Canal -> (Sélectionner un canal) -> Intégrations -> Ajouter une nouvelle page d'intégration, ajouter un \"événement personnalisé\" pour obtenir une adresse push, copier la clé d'intégration dans l'adresse. Pour plus d'informations, veuillez visiter",
"Request Timeout": "Délai d'expiration de la demande",
"timeoutAfter": "Délai dépassé après {0} secondes",
"gamedigGuessPort": "Gamedig: Devinez le port",
"gamedigGuessPort": "Gamedig : Devinez le port",
"gamedigGuessPortDescription": "Le port utilisé par Valve Server Query Protocol peut être différent du port client. Essayez ceci si la sonde ne peut pas se connecter à votre serveur.",
"styleElapsedTimeShowNoLine": "Afficher (pas de ligne)",
"styleElapsedTimeShowWithLine": "Afficher (avec ligne)",
"styleElapsedTime": "Temps écoulé sous la barre d'état"
"styleElapsedTime": "Temps écoulé sous la barre d'état",
"enableNSCD": "Activer NSCD (Name Service Cache Daemon) pour mettre en cache toutes les demandes DNS",
"setupDatabaseChooseDatabase": "Quelle base de données souhaitez-vous utiliser ?",
"setupDatabaseEmbeddedMariaDB": "Vous n'avez pas besoin de régler quoi que ce soit. Cette image docker a intégré et configuré automatiquement une MariaDB pour vous. Uptime Kuma se connectera à cette base de données via la socket unix.",
"setupDatabaseSQLite": "Un fichier de base de données simple, recommandé pour les déploiements à petite échelle. Avant la v2.0.0, Uptime Kuma utilisait SQLite comme base de données par défaut.",
"setupDatabaseMariaDB": "Connectez-vous à une base de données MariaDB externe. Vous devez définir les informations de connexion à la base de données.",
"dbName": "Nom de la base de données",
"Saved.": "Enregistré.",
"toastErrorTimeout": "Délai d'attente pour les notifications d'erreur",
"toastSuccessTimeout": "Délai d'attente pour les notifications de réussite",
"monitorToastMessagesLabel": "Surveiller les notifications Toast",
"monitorToastMessagesDescription": "Les notifications Toast pour les sondes disparaissent après un délai donné en secondes. La valeur -1 désactive le délai d'attente. La valeur 0 désactive les notifications toast.",
"Bark API Version": "Version de l'API Bark",
"pushViewCode": "Comment utiliser une sonde type « Push » (voir le code)",
"pushOthers": "Autres",
"programmingLanguages": "Langages de programmation",
"authInvalidToken": "Jeton invalide.",
"authIncorrectCreds": "Nom d'utilisateur ou mot de passe incorrects.",
"2faAlreadyEnabled": "L'authentification à deux facteurs (2FA) est déjà activée.",
"2faDisabled": "Authentification à deux facteurs (2FA) désactivée.",
"successAdded": "Ajouté avec succès.",
"successEdited": "Modifié avec succès.",
"successBackupRestored": "Sauvegarde restaurée avec succès.",
"successDisabled": "Désactivé avec succès.",
"successEnabled": "Activé avec succès.",
"tagNotFound": "Étiquette non trouvée.",
"foundChromiumVersion": "Version de Chromium/Chrome trouvée : {0}",
"successResumed": "Reprise avec succès.",
"successPaused": "Mis en pause avec succès.",
"authUserInactiveOrDeleted": "L'utilisateur est inactif ou a été supprimé.",
"2faEnabled": "Authentification à deux facteurs (2FA) activée.",
"successDeleted": "Supprimé avec succès.",
"successAuthChangePassword": "Le mot de passe a bien été mis à jour."
}

View File

@@ -102,7 +102,7 @@
"Enable Auth": "Omogući autentikaciju",
"disableauth.message1": "Jeste li sigurni da želite <strong>isključiti autentikaciju</strong>?",
"disableauth.message2": "To je za <strong>korisnike koji imaju vanjsku autentikaciju stranice</strong> ispred Uptime Kume, poput usluge Cloudflare Access.",
"Please use this option carefully!": "Pažljivo koristite ovu opciju.",
"Please use this option carefully!": "Pažljivo koristite ovu opciju!",
"Logout": "Odjava",
"Leave": "Poništi",
"I understand, please disable": "Razumijem, svejedno onemogući",
@@ -158,8 +158,8 @@
"Token": "Token",
"Show URI": "Pokaži URI",
"Tags": "Oznake",
"Add New below or Select...": "Dodajte novu oznaku ispod ili odaberite...",
"Tag with this name already exist.": "Oznaka s tim nazivom već postoji",
"Add New below or Select...": "Dodajte novu oznaku ispod ili odaberite",
"Tag with this name already exist.": "Oznaka s tim nazivom već postoji.",
"Tag with this value already exist.": "Oznaka s tom vrijednošću već postoji.",
"color": "Boja",
"value (optional)": "Vrijednost (neobavezno)",
@@ -171,7 +171,7 @@
"Indigo": "Indigo",
"Purple": "Ljubičasta",
"Pink": "Ružičasta",
"Search...": "Pretraga...",
"Search...": "Pretraga",
"Avg. Ping": "Prosječni odziv",
"Avg. Response": "Prosječni odgovor",
"Entry Page": "Početna stranica",
@@ -213,10 +213,10 @@
"smtpBCC": "Bcc",
"discord": "Discord",
"Discord Webhook URL": "URL Discord webhooka",
"wayToGetDiscordURL": "Ovo možete dobiti tako da odete na Postavke servera -> Integracije -> Napravi webhook",
"wayToGetDiscordURL": "Ovo možete dobiti tako da odete na Postavke servera -> Integracije -> Pogledaj webhookove -> Novi webhook",
"Bot Display Name": "Nadimak Bota unutar servera",
"Prefix Custom Message": "Prefiks prilagođene poruke",
"Hello @everyone is...": "Pozdrav {'@'}everyone...",
"Hello @everyone is...": "Pozdrav {'@'}everyone",
"teams": "Microsoft Teams",
"Webhook URL": "URL webhooka",
"wayToGetTeamsURL": "Više informacija o Teams webhookovima možete pročitati {0}.",
@@ -315,8 +315,8 @@
"Info": "Informacije",
"Security": "Sigurnost",
"Shrink Database": "Smanji bazu podataka",
"Pick a RR-Type...": "Odaberite vrstu DNS zapisa od navedenih...",
"Pick Accepted Status Codes...": "Odaberite HTTP statusne kodove koji će biti prihvaćeni...",
"Pick a RR-Type...": "Odaberite vrstu DNS zapisa od navedenih",
"Pick Accepted Status Codes...": "Odaberite HTTP statusne kodove koji će biti prihvaćeni",
"Steam API Key": "Steam API ključ",
"Default": "Zadano",
"HTTP Options": "HTTP Postavke",
@@ -348,7 +348,7 @@
"Discard": "Odbaci",
"Cancel": "Otkaži",
"Powered by": "Pokreće",
"Saved": "Spremljeno",
"Saved.": "Spremljeno.",
"PushByTechulus": "Push by Techulus",
"GoogleChat": "Google Chat (preko platforme Google Workspace)",
"shrinkDatabaseDescription": "Pokreni VACUUM operaciju za SQLite. Ako je baza podataka kreirana nakon inačice 1.10.0, AUTO_VACUUM opcija već je uključena te ova akcija nije nužna.",
@@ -385,7 +385,7 @@
"successMessageExplanation": "MQTT poruka koja se smatra uspješnom",
"error": "greška",
"critical": "kritično",
"Customize": "Customize",
"Customize": "Prilagodi",
"Custom Footer": "Prilagođeno podnožje",
"Custom CSS": "Prilagođeni CSS",
"wayToGetPagerDutyKey": "Ključ možete dobiti odlaskom na \"Service -> Service Directory -> (Odabrani servis) -> Integrations -> Add integration\". Ovdje pretražite za \"Events API V2\". Više informacija {0}",
@@ -406,7 +406,7 @@
"Certificate Chain": "Lanac certifikata",
"Valid": "Važeći",
"Invalid": "Nevažeći",
"AccessKeyId": "AccessKey ID",
"AccessKeyId": "AccessKey identifikator",
"SecretAccessKey": "AccessKey tajni ključ",
"PhoneNumbers": "Telefonski brojevi",
"TemplateCode": "Predložak koda",
@@ -415,7 +415,7 @@
"Bark Endpoint": "Bark krajnja točka (endpoint)",
"Bark Group": "Bark grupa",
"Bark Sound": "Bark zvuk",
"WebHookUrl": "WebHookUrl",
"WebHookUrl": "URL webhooka",
"SecretKey": "Tajni ključ",
"For safety, must use secret key": "Korištenje tajnog ključa je obavezno",
"Device Token": "Token uređaja",
@@ -447,7 +447,7 @@
"The slug is already taken. Please choose another slug.": "Slug je zauzet. Odaberite novi slug.",
"No Proxy": "Bez proxy poslužitelja",
"Authentication": "Autentikacija",
"HTTP Basic Auth": "HTTP Basic Auth",
"HTTP Basic Auth": "HTTP \"Basic\" autentifikacija",
"New Status Page": "Dodaj statusnu stranicu",
"Page Not Found": "Stranica nije pronađena",
"Reverse Proxy": "Reverzni proxy",
@@ -497,36 +497,36 @@
"Recipient Number": "Broj primatelja",
"From Name/Number": "Naziv/broj pošiljatelja",
"Leave blank to use a shared sender number.": "Ostaviti prazno za korištenje dijeljenog broja pošiljatelja.",
"Octopush API Version": "Octopush verzija API-ja",
"Legacy Octopush-DM": "Legacy Octopush-DM",
"Octopush API Version": "Verzija Octopush API-ja",
"Legacy Octopush-DM": "Zastarijela Octopush-DM",
"endpoint": "krajnja točka (endpoint)",
"octopushAPIKey": "\"API ključ\" iz HTTP API postavki",
"octopushAPIKey": "\"API ključ\" iz HTTP API postavki na upravljačkoj ploči",
"octopushLogin": "\"Korisničko ime\" iz HTTP API postavki",
"promosmsLogin": "API korisničko ime",
"promosmsPassword": "API lozinka",
"pushoversounds pushover": "Pushover (default)",
"pushoversounds bike": "Bike",
"pushoversounds bugle": "Bugle",
"pushoversounds cashregister": "Cash Register",
"pushoversounds pushover": "Pushover (zadano)",
"pushoversounds bike": "Bicikl",
"pushoversounds bugle": "Truba",
"pushoversounds cashregister": "Blagajna",
"pushoversounds classical": "Classical",
"pushoversounds cosmic": "Cosmic",
"pushoversounds falling": "Falling",
"pushoversounds cosmic": "Kozmički",
"pushoversounds falling": "Padanje",
"pushoversounds gamelan": "Gamelan",
"pushoversounds incoming": "Incoming",
"pushoversounds intermission": "Intermission",
"pushoversounds magic": "Magic",
"pushoversounds mechanical": "Mechanical",
"pushoversounds pianobar": "Piano Bar",
"pushoversounds siren": "Siren",
"pushoversounds spacealarm": "Space Alarm",
"pushoversounds tugboat": "Tug Boat",
"pushoversounds alien": "Alien Alarm (long)",
"pushoversounds climb": "Climb (long)",
"pushoversounds persistent": "Persistent (long)",
"pushoversounds echo": "Pushover Echo (long)",
"pushoversounds updown": "Up Down (long)",
"pushoversounds vibrate": "Vibrate Only",
"pushoversounds none": "None (silent)",
"pushoversounds incoming": "Dolazno",
"pushoversounds intermission": "Intermisija",
"pushoversounds magic": "Čarolija",
"pushoversounds mechanical": "Mehanički",
"pushoversounds pianobar": "Bar s klavirom",
"pushoversounds siren": "Sirena",
"pushoversounds spacealarm": "Svemirski alarm",
"pushoversounds tugboat": "Remorker",
"pushoversounds alien": "Vanzemaljski alarm (dugačko)",
"pushoversounds climb": "Penjanje (dugačko)",
"pushoversounds persistent": "Uporno (dugačko)",
"pushoversounds echo": "Pushover jeka (dugačko)",
"pushoversounds updown": "Gore-dolje (dugačko)",
"pushoversounds vibrate": "Samo vibracija",
"pushoversounds none": "Utišano (bez zvuka)",
"pushyAPIKey": "Tajni API ključ",
"pushyToken": "Token uređaja",
"Show update if available": "Pokaži moguću nadogradnju",
@@ -575,5 +575,294 @@
"Event data:": "Podaci eventa:",
"Then choose an action, for example switch the scene to where an RGB light is red.": "Potrebno je i odabrati akciju za izvođenje na Home Assistantu.",
"Frontend Version": "Inačica sučelja",
"Frontend Version do not match backend version!": "Inačica sučelja ne odgovara poslužitelju!"
"Frontend Version do not match backend version!": "Inačica sučelja ne odgovara poslužitelju!",
"monitorToastMessagesLabel": "Skočne obavijesti Monitora",
"toastSuccessTimeout": "Vrijeme isteka skočnih obavijesti o uspjehu",
"toastErrorTimeout": "Vrijeme isteka skočnih obavijesti o pogrešci",
"Enter the list of brokers": "Upišite popis brokera",
"Press Enter to add broker": "Pritisnite Enter za dodavanje brokera",
"Kafka Topic Name": "Naziv Kafka teme",
"Kafka Producer Message": "Poruka Kafka producera",
"Enable Kafka SSL": "Omogući SSL",
"Enable Kafka Producer Auto Topic Creation": "Omogući automatsku izradu teme za Kafka producera",
"Kafka SASL Options": "Kafka SASL opcije",
"Mechanism": "Mehanizam",
"Pick a SASL Mechanism...": "Odaberite SASL mehanizam...",
"AccessKey Id": "ID pristupnog ključa",
"Secret AccessKey": "Tajni pristupni ključ",
"Session Token": "Token sesije",
"Schedule maintenance": "Zakaži održavanje",
"Select status pages...": "Odaberi statusne stranice…",
"webhookAdditionalHeadersTitle": "Dodatna zaglavlja",
"webhookAdditionalHeadersDesc": "Postavlja dodatna polja zaglavlja koja se šalju s webhookom. Svako zaglavlje treba se definirati kao JSON par ključ-vrijednost.",
"Packet Size": "Veličina paketa",
"backupRecommend": "Umjesto toga napravite ručnu sigurnosnu kopiju cijelog volumena ili izravno direktorija s podacima (./data/).",
"No Maintenance": "Ne postoje zakazana održavanja",
"Server Timezone": "Vremenska zona poslužitelja",
"dnsCacheDescription": "Možda ne radi kako spada u nekim IPv6 okruženjima. Onemogućite ako naiđete na probleme.",
"Select": "Odaberi",
"tailscalePingWarning": "Kako biste koristili Tailscale Ping monitor, trebate instalirati Uptime Kuma bez Dockera te također instalirati Tailscale klijent na svoj poslužitelj.",
"telegramProtectContentDescription": "Ako je omogućeno, poruke bota će biti zaštićene od prosljeđivanja i spremanja.",
"enableNSCD": "Omogući NSCD (Name Service Cache Daemon) za sve DNS zahtjeve",
"chromeExecutableDescription": "Za korisnike Dockera, ako Chromium još nije instaliran, instalacija i prikaz rezultata testa može potrajati nekoliko minuta. Zauzima 1 GB prostora na disku.",
"grpcMethodDescription": "Naziv metode automatski se pretvara u camelCase format. Primjerice, \"say hello\" će se pretvoriti u \"sayHello\".",
"wayToGetKookBotToken": "Kreirajte aplikaciju i preuzmite token svog bota na {0}",
"wayToGetKookGuildID": "Uključite 'Developer Mode' za Kook u postavkama i desnom tipkom miša kliknite na guild kako biste dobili njegov ID",
"Lowcost": "Niskotarifni",
"SendKey": "Ključ za slanje (SendKey)",
"You can divide numbers with": "Možete odvojiti brojeve pomoću",
"goAlertInfo": "GoAlert je aplikacija otvorenog koda za zakazivanje poziva, automatiziranu eskalaciju i slanje obavijesti (poput SMS-a ili glasovnih poziva). Automatski obavijestite pravu osobu, na pravi način i u pravo vrijeme! {0}",
"smseagleTo": "Broj(evi) telefona",
"smseagleGroup": "Nazivi grupa telefonskog imenika",
"smseagleRecipient": "Primatelj(i) (višestruke vrijednosti moraju se odvojiti zarezom)",
"pushDeerServerDescription": "Ostavite prazno za korištenje službenog poslužitelja",
"Edit Tag": "Uredi oznaku",
"Expiry date": "Datum isteka",
"Schedule Maintenance": "Zakazivanje održavanja",
"Edit Maintenance": "Uređivanje održavanja",
"uninstall": "Deinstaliraj",
"uninstalling": "Deinstalacija",
"Badge Type": "Tip značke",
"apiKeyAddedMsg": "Vaš API ključ je dodan. Spremite ga sada jer se više neće prikazivati.",
"pagertreeDoNothing": "Ne čini ništa",
"wayToGetPagerTreeIntegrationURL": "Nakon što stvorite Uptime Kuma integraciju u aplikaciji PagerTree, kopirajte krajnju točku (endpoint). Pogledajte sve pojedinosti {0}",
"Show Clickable Link Description": "Ako je označeno, svi koji imaju pristup ovoj statusnoj stranici mogu imati pristup URL-u Monitora.",
"monitorToastMessagesDescription": "Skočne obavijesti za monitore nestaju nakon zadanog vremena u sekundama. Vrijednost od -1 onemogućuje vremensko ograničenje, 0 onemogućuje skočne obavijesti.",
"Authorization Identity": "Identitet autorizacije",
"weekdayShortThu": "Čet",
"setupDatabaseChooseDatabase": "Koju bazu podataka želite koristiti?",
"setupDatabaseEmbeddedMariaDB": "Ne morate ništa dodatno postavljati. Ovaj docker image ima ugrađenu i konfiguriranu MariaDB bazu podataka za Vas. Uptime Kuma će se spojiti na ovu bazu preko UNIX socketa.",
"setupDatabaseMariaDB": "Spojite vanjsku MariaDB bazu podataka. Morate unijeti informacije o konekciji prema bazi.",
"setupDatabaseSQLite": "Jednostavna datoteka s bazom podataka, preporuča se samo za manje implementacije. Prije inačice v2.0.0, Uptime Kuma je koristila SQLite kao zadanu bazu podataka.",
"dbName": "Naziv baze podataka",
"Start of maintenance": "Početak održavanja",
"All Status Pages": "Sve statusne stranice",
"Affected Monitors": "Zahvaćeni Monitori",
"Pick Affected Monitors...": "Odaberi zahvaćene Monitore…",
"filterActivePaused": "Pauzirano",
"Add New Tag": "Dodaj novu oznaku",
"statusPageRefreshIn": "Osvježavanje za: {0}",
"webhookCustomBodyDesc": "Prilagodite tijelo HTTP zahtjeva. Dozvoljene varijable predloška: {msg}, {heartbeat}, {monitor}.",
"webhookBodyPresetOption": "Unaprijed postavljeno - {0}",
"webhookBodyCustomOption": "Prilagođeno tijelo zahtjeva",
"selectedMonitorCount": "Odabrano: {0}",
"Check/Uncheck": "Označi/odznači",
"telegramMessageThreadID": "(Neobavezno) ID dretve poruka",
"telegramMessageThreadIDDescription": "Neobavezni jedinstveni identifikator za dretvu poruka (temu) foruma; samo za forumske supergrupe",
"telegramSendSilently": "Pošalji nečujno",
"telegramSendSilentlyDescription": "Šalje poruku nečujno. Primatelji će dobiti obavijest bez zvuka.",
"telegramProtectContent": "Zaštiti od prosljeđivanja i spremanja",
"Optional": "Neobavezno",
"or": "ili",
"weekdayShortTue": "Uto",
"weekdayShortWed": "Sri",
"weekdayShortFri": "Pet",
"weekdayShortSat": "Sub",
"dayOfWeek": "Dan u tjednu",
"dayOfMonth": "Dan u mjesecu",
"lastDay": "Posljednji dan",
"lastDay1": "Posljednji dan mjeseca",
"lastDay2": "Pretposljednji dan mjeseca",
"lastDay3": "Treći do posljednjeg dana u mjesecu",
"lastDay4": "Četvrti do posljednjeg dana u mjesecu",
"pauseMaintenanceMsg": "Jeste li sigurni da želite pauzirati?",
"maintenanceStatus-under-maintenance": "Održavanje u tijeku",
"maintenanceStatus-inactive": "Neaktivno",
"maintenanceStatus-scheduled": "Zakazano",
"maintenanceStatus-ended": "Završeno",
"maintenanceStatus-unknown": "Nepoznato",
"Display Timezone": "Prikaži vremensku zonu",
"statusPageMaintenanceEndDate": "Kraj",
"IconUrl": "URL ikone",
"Enable": "Omogući",
"Disable": "Onemogući",
"sameAsServerTimezone": "Ista kao i vremenska zona poslužitelja",
"chromeExecutable": "Izvršna datoteka za Chrome/Chromium",
"chromeExecutableAutoDetect": "Automatska detekcija",
"Single Maintenance Window": "Jednokratno održavanje",
"Maintenance Time Window of a Day": "Vrijeme održavanja u danu",
"Effective Date Range": "Efektivan raspon datuma (neobavezno)",
"Clone": "Kloniraj",
"dataRetentionTimeError": "Razdoblje zadržavanja mora biti 0 ili veće",
"infiniteRetention": "Postavite na 0 za beskonačno zadržavanje.",
"confirmDeleteTagMsg": "Jeste li sigurni da želite izbrisati ovu oznaku? Monitori povezani s ovom oznakom neće biti izbrisani.",
"enableGRPCTls": "Omogući sigurno slanje gRPC zahtjeva koristeći TLS",
"deleteMaintenanceMsg": "Jeste li sigurni da želite izbrisati ovo održavanje?",
"Guild ID": "ID za guild",
"pushoverMessageTtl": "Vrijeme isteka poruke (u sekundama)",
"Proto Method": "Metoda poziva",
"Proto Content": "Proto sadržaj",
"Economy": "Ekonomski",
"high": "visoko",
"SMSManager API Docs": "Dokumentacija SMSManager API-ja ",
"Gateway Type": "Tip poveznika (gateway)",
"Base URL": "Osnovni URL",
"goAlertIntegrationKeyInfo": "Nabavite generički integracijski API ključ za ovu uslugu u formatu \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\", koji je obično vrijednost token parametra kopiranog URL-a.",
"aboutNotifyChannel": "Ova opcija će poslati mobilnu ili desktop obavijest za sve članove kanala, bez obzira na to jesu li trenutno dostupni ili ne.",
"smseagleContact": "Nazivi kontakata telefonskog imenika",
"smseagleRecipientType": "Tip primatelja",
"smseagleToken": "Token za pristup API-ju",
"smseagleUrl": "URL Vašeg SMSEagle uređaja",
"smseagleEncoding": "Pošalji kao Unicode",
"smseaglePriority": "Prioritet poruke (0-9, zadana vrijednost je 0)",
"Server URL should not contain the nfty topic": "URL poslužitelja ne smije sadržavati temu nfty",
"PushDeer Server": "PushDeer poslužitelj",
"Custom Monitor Type": "Prilagođeni tip Monitora",
"Google Analytics ID": "Google Analytics identifikator",
"Server Address": "Adresa poslužitelja",
"Learn More": "Saznaj više",
"Body Encoding": "Vrsta sadržaja u tijelu zahtjeva",
"API Keys": "API ključevi",
"Expiry": "Istek",
"Don't expire": "Bez isteka",
"Continue": "Nastavi",
"Add Another": "Dodaj još jedan",
"Key Added": "Ključ dodan",
"Add API Key": "Dodaj API ključ",
"No API Keys": "Nema dodanih API ključeva",
"apiKey-active": "Aktivan",
"apiKey-expired": "Istekao",
"apiKey-inactive": "Neaktivan",
"Expires": "Ističe",
"disableAPIKeyMsg": "Jeste li sigurni da želite onemogućiti ovaj API ključ?",
"Generate": "Generiraj",
"pagertreeIntegrationUrl": "URL integracije",
"pagertreeUrgency": "Hitnost",
"pagertreeSilent": "Utišano",
"pagertreeLow": "Niska",
"pagertreeMedium": "Srednja",
"pagertreeHigh": "Visoka",
"pagertreeCritical": "Kritična",
"pagertreeResolve": "Automatsko rješavanje",
"lunaseaTarget": "Cilj",
"lunaseaDeviceID": "ID uređaja",
"lunaseaUserID": "Korisnički ID",
"ntfyAuthenticationMethod": "Metoda provjere autentičnosti",
"ntfyUsernameAndPassword": "Korisničko ime i lozinka",
"twilioAccountSID": "SID korisničkog računa",
"twilioAuthToken": "Token za autentifikaciju / tajni API ključ",
"twilioFromNumber": "Broj pošiljatelja",
"twilioToNumber": "Broj primatelja",
"Show Clickable Link": "Pokaži poveznicu",
"Open Badge Generator": "Otvori generator znački",
"Badge Duration (in hours)": "Trajanje značke (u satima)",
"Badge Prefix": "Prefiks vrijednosti značke",
"Badge Suffix": "Sufiks vrijednosti značke",
"Badge Label": "Natpis značke",
"Badge Label Color": "Boja natpisa značke",
"Badge Color": "Boja značke",
"Badge Label Prefix": "Prefiks natpisa značke",
"Badge Preview": "Pretpregled značke",
"Badge Label Suffix": "Sufiks natpisa značke",
"Badge Up Color": "Boja značke za dostupnost",
"Badge Down Color": "Boja značke za nedostupnost",
"Badge Pending Color": "Boja značke za monitore u tijeku",
"Badge Maintenance Color": "Boja značke za monitore u održavanju",
"Badge Warn Color": "Boja značke za upozorenje",
"Badge Warn Days": "Dani značke za upozorenje",
"Badge Down Days": "Dani značke za nedostupnost",
"Badge value (For Testing only.)": "Vrijednost značke (samo za testiranje)",
"Badge URL": "URL značke",
"Group": "Grupa",
"Monitor Group": "Grupa Monitora",
"Badge Style": "Stil značke",
"Custom": "Prilagođeno",
"styleElapsedTime": "Vremenske oznake ispod trake dostupnosti",
"styleElapsedTimeShowNoLine": "Pokaži (bez linije)",
"styleElapsedTimeShowWithLine": "Pokaži (s linijom)",
"recurringInterval": "Periodično",
"Recurring": "Ponavljajući",
"strategyManual": "Ručno aktivan/neaktivan",
"warningTimezone": "Koristi vremensku zonu poslužitelja",
"weekdayShortMon": "Pon",
"weekdayShortSun": "Ned",
"startDateTime": "Vrijeme početka",
"endDateTime": "Vrijeme završetka",
"cronExpression": "Cron izraz",
"cronSchedule": "Raspored: ",
"invalidCronExpression": "Nevaljali Cron izraz: {0}",
"Date and Time": "Datum i vrijeme",
"DateTime Range": "Vremenski raspon",
"loadingError": "Nije moguće dohvatiti podatke, pokušajte ponovno kasnije.",
"plugin": "Dodatak | Dodaci",
"install": "Instaliraj",
"installing": "Instaliranje",
"confirmUninstallPlugin": "Jeste li sigurni da želite deinstalirati ovaj dodatak?",
"notificationRegional": "Specifično za regiju",
"Clone Monitor": "Kloniraj Monitor",
"cloneOf": "Klon monitora {0}",
"wayToGetZohoCliqURL": "Možete naučiti kako kreirati URL za webhook {0}.",
"affectedMonitorsDescription": "Odaberite monitore koji će biti zahvaćeni održavanjem",
"recurringIntervalMessage": "Pokreni jednom svaki dan | Pokreni jednom svakih {0} dana",
"affectedStatusPages": "Prikazuje poruku o održavanju na odabranim statusnim stranicama",
"atLeastOneMonitor": "Odaberite barem jedan zahvaćeni Monitor",
"invertKeywordDescription": "Postavi da ključna riječ mora biti odsutna umjesto prisutna.",
"jsonQueryDescription": "Izvršite JSON upit nad primljenim odgovorom i provjerite očekivanu povrtanu vrijednost. Ona će se za usporedbu pretvoriti u niz znakova (string). Pogledajte stranicu <a href='https://jsonata.org/'>jsonata.org</a> za dokumentaciju o jeziku upita. Testno okruženje možete pronaći <a href='https://try.jsonata.org/'>ovdje</a>.",
"Strategy": "Strategija",
"Free Mobile User Identifier": "Besplatni mobilni korisnički identifikator",
"Free Mobile API Key": "Besplatni mobilni ključ za API",
"Enable TLS": "Omogući TLS",
"Proto Service Name": "Naziv servisa",
"promosmsAllowLongSMS": "Dozvoli dugačke SMS-ove",
"Notify Channel": "Obavijesti cijeli kanal",
"Request Timeout": "Vrijeme isteka zahtjeva",
"timeoutAfter": "Istek nakon {0} sekundi",
"backupOutdatedWarning": "Zastarjelo: Budući da je dodano puno značajki, a ova je pomalo neodržavana, ne može generirati niti vratiti potpunu sigurnosnu kopiju.",
"Enable DNS Cache": "(Zastarijelo) Omogući DNS privremenu memoriju (cache) za HTTP(s) monitore",
"Home": "Početna",
"deleteAPIKeyMsg": "Jeste li sigurni da želite obrisati ovaj API ključ?",
"twilioApiKey": "API ključ (neobavezno)",
"Kafka Brokers": "Kafka brokeri",
"Game": "Igra",
"Passive Monitor Type": "Pasivni tip Monitora",
"markdownSupported": "Podržana je Markdown sintaksa",
"statusMaintenance": "Održavanje",
"General Monitor Type": "Općeniti tip Monitora",
"Maintenance": "Održavanje",
"Specific Monitor Type": "Određeni tip Monitora",
"Monitor": "Monitor | Monitori",
"Invert Keyword": "Obrni ključnu riječ",
"filterActive": "Aktivnost",
"Cannot connect to the socket server": "Nije moguće spojiti se na socket poslužitelj",
"Reconnecting...": "Ponovno povezivanje...",
"Expected Value": "Očekivana vrijednost",
"Json Query": "JSON upit",
"Help": "Pomoć",
"noGroupMonitorMsg": "Nije dostupno. Prvo kreirajte grupu Monitora.",
"Close": "Zatvori",
"Request Body": "Tijelo zahtjeva",
"wayToGetFlashDutyKey": "Možete otići na 'Channel' -> (Odaberite kanal) -> 'Integrations' -> 'Add a new integration' i odaberite 'Custom Event' da biste dobili push adresu. Zatim kopirajte integracijski ključ u adresu. Za više informacija posjetite",
"FlashDuty Severity": "Stupanj ozbiljnosti",
"nostrRelays": "Nostr releji",
"nostrRelaysHelp": "Jedan URL releja po liniji",
"nostrSender": "Privatni ključ pošiljatelja (nsec)",
"nostrRecipients": "Javni ključevi primatelja (npub)",
"nostrRecipientsHelp": "U formatu npub, jedan ključ po liniji",
"showCertificateExpiry": "Pokaži istek certifikata",
"noOrBadCertificate": "Nepostojeći ili nevaljali certifikat",
"gamedigGuessPort": "Gamedig: Pogodi vrijednost porta",
"gamedigGuessPortDescription": "Port koji koristi Valve Server Query Protocol može se razlikovati od klijentskog porta. Pokušajte uključiti ovu opciju ako Monitor ne može uspostaviti vezu s vašim poslužiteljem.",
"Monitor Setting": "Postavka monitora korisnika {0}",
"Badge Generator": "Generator znački korisnika {0}",
"Bark API Version": "Verzija Bark API-ja",
"authInvalidToken": "Nevažeći token.",
"authIncorrectCreds": "Pogrešno korisničko ime ili lozinka.",
"2faAlreadyEnabled": "Dvofaktorska autentifikacija je već omogućena.",
"2faDisabled": "Dvofaktorska autentifikacija je onemogućena.",
"successAdded": "Uspješno dodano.",
"successPaused": "Uspješno pauzirano.",
"successEdited": "Uspješno uređeno.",
"successAuthChangePassword": "Lozinka je uspješno ažurirana.",
"successBackupRestored": "Sigurnosna kopija je uspješno vraćena.",
"successDisabled": "Uspješno onemogućeno.",
"successEnabled": "Uspješno omogućeno.",
"foundChromiumVersion": "Pronađen program Chromium ili Chrome. Inačica: {0}",
"pushViewCode": "Kako koristiti Monitor Push? (Prikaži kôd)",
"programmingLanguages": "Programski jezici",
"authUserInactiveOrDeleted": "Korisnik je neaktivan ili obrisan.",
"2faEnabled": "Dvofaktorska autentifikacija je omogućena.",
"successResumed": "Uspješno nastavljeno.",
"successDeleted": "Uspješno obrisano.",
"tagNotFound": "Oznaka nije pronađena.",
"pushOthers": "Ostali"
}

View File

@@ -31,7 +31,7 @@
"confirmDisableTwoFAMsg": "Apakah Anda yakin ingin menonaktifkan 2FA?",
"Settings": "Pengaturan",
"Dashboard": "Dasbor",
"New Update": "Update terbaru",
"New Update": "Pembaruan Baru",
"Language": "Bahasa",
"Appearance": "Tampilan",
"Theme": "Tema",
@@ -584,7 +584,7 @@
"Game": "Permainan",
"markdownSupported": "Dukungan sintaks markdown",
"statusMaintenance": "Pemeliharaan",
"Maintenance": "Pemeliharaan",
"Maintenance": "Tidak Ada Pemeliharaan",
"General Monitor Type": "Tipe Monitor Umum",
"Passive Monitor Type": "Tipe Monitor Pasif",
"Specific Monitor Type": "Tipe Monitor Spesifik",
@@ -788,5 +788,10 @@
"Badge Pending Color": "Warna Lencana Tertunda",
"Monitor Group": "Monitor Grup",
"Expected Value": "Value yang diharapkan",
"Json Query": "Json Query"
"Json Query": "Json Query",
"setupDatabaseSQLite": "File database sederhana, direkomendasikan untuk penerapan skala kecil. Sebelum v2.0.0, Uptime Kuma menggunakan SQLite sebagai database default.",
"setupDatabaseMariaDB": "Hubungkan ke database MariaDB eksternal. Anda perlu mengatur informasi koneksi database.",
"setupDatabaseEmbeddedMariaDB": "Anda tidak perlu mengatur apa pun. Docker Image ini telah menyematkan dan mengonfigurasi MariaDB untuk Anda secara otomatis. Uptime Kuma akan terhubung ke database ini melalui soket unix.",
"setupDatabaseChooseDatabase": "Database mana yang ingin Anda gunakan?",
"dbName": "Nama Database"
}

View File

@@ -610,5 +610,6 @@
"Expected Value": "Valore atteso",
"Json Query": "Query Json",
"deleteMaintenanceMsg": "Sei sicuro di voler cancellare questa attività di manutenzione?",
"dnsPortDescription": "Porta server DNS. Default 53. Puoi cambiare questa porta in ogni momento."
"dnsPortDescription": "Porta server DNS. Default 53. Puoi cambiare questa porta in ogni momento.",
"setupDatabaseChooseDatabase": "Quale database vuoi usare?"
}

View File

@@ -552,5 +552,18 @@
"Lowcost": "低コスト",
"A list of Notification Services can be found in Home Assistant under \"Developer Tools > Services\" search for \"notification\" to find your device/phone name.": "通知サービスの一覧からデバイス名を見つけるには、Home Assistantの「開発者ツール > サービス」から通知で検索してください。",
"Notify Channel": "通知チャンネル",
"Icon Emoji": "絵文字アイコン"
"Icon Emoji": "絵文字アイコン",
"setupDatabaseChooseDatabase": "どのデータベースを利用しますか?",
"setupDatabaseEmbeddedMariaDB": "何も設定する必要はありません。この Docker イメージは設定済みの MariaDB が組み込まれています。Uptime Kuma はこのデータベースに unix ソケットを通じて接続します。",
"setupDatabaseSQLite": "小規模な開発のために推奨される小さなデータベースファイルです。v2.0.0 以前は、Uptime Kuma は SQLite を標準のデータベースとして使用していました。",
"tailscalePingWarning": "Tailscale Ping モニターを使用するためには、Uptime Kuma を Docker を利用せずインストールし、そのうえで Tailscale Client をサーバーにインストールしてください。",
"invertKeywordDescription": "キーワードが含まれているものではなく、含まれないものを探します。",
"setupDatabaseMariaDB": "外部の MariaDB データベースに接続するためには、データベースの接続情報を設定する必要があります。",
"dbName": "データベース名",
"Request Timeout": "リクエストタイムアウト",
"timeoutAfter": "{0} 秒後にタイムアウト",
"selectedMonitorCount": "選択済み: {0}",
"Long-Lived Access Token": "Long-Lived Access Token",
"Invert Keyword": "「含まない」キーワード",
"Expected Value": "期待値"
}

View File

@@ -54,7 +54,7 @@
"Delete": "삭제",
"Current": "현재",
"Uptime": "업타임",
"Cert Exp.": "인증서 만료",
"Cert Exp.": "인증서 만료.",
"day": "일",
"-day": "-일",
"hour": "시간",
@@ -84,7 +84,7 @@
"Theme - Heartbeat Bar": "테마 - 하트비트 바",
"Normal": "기본값",
"Bottom": "가운데",
"None": "없애기",
"None": "없",
"Timezone": "시간대",
"Search Engine Visibility": "검색 엔진 활성화",
"Allow indexing": "인덱싱 허용",
@@ -749,5 +749,15 @@
"lunaseaDeviceID": "기기 ID",
"statusPageRefreshIn": "{0} 후 새로고침",
"telegramMessageThreadIDDescription": "포럼의 대상 메시지 쓰레드(주제)에 대한 선택적 고유 식별인, 포럼 관리자 그룹에만 해당",
"pagertreeSilent": "없음"
"pagertreeSilent": "없음",
"setupDatabaseChooseDatabase": "어떤 데이터베이스를 사용하시겠습니까?",
"setupDatabaseEmbeddedMariaDB": "추가 설정은 필요 없습니다. 이 도커 이미지는 MariaDB가 내장되어 구성되어 있습니다. Uptime Kuma는 Unix Socket을 통해 데이터베이스에 연결합니다.",
"setupDatabaseMariaDB": "외부 MariaDB 데이터베이스에 연결합니다. 데이터베이스 연결 정보를 설정해야 합니다.",
"setupDatabaseSQLite": "소규모 배포에 권장되는 간단한 데이터베이스 파일입니다. v2.0.0 이전에는 Uptime Kuma가 SQLite를 기본 데이터베이스로 사용했습니다.",
"dbName": "데이터베이스 이름",
"filterActive": "활성화",
"filterActivePaused": "일시중지",
"Home": "홈",
"Cannot connect to the socket server": "소켓 서버에 연결 할 수 없습니다",
"Reconnecting...": "재 연결중..."
}

View File

@@ -24,5 +24,30 @@
"Dashboard": "Papan pemuka",
"Language": "Bahasa",
"Add New Monitor": "Tambah monitor baharu",
"Passive Monitor Type": "Jenis monitor pasif"
"Passive Monitor Type": "Jenis monitor pasif",
"No Services": "Tiada Servis",
"Add a monitor": "Tambah Monitor",
"High": "Tinggi",
"Normal": "Biasa",
"Bottom": "Bawah",
"None": "None",
"All Systems Operational": "Semua Sistem Baik",
"Partially Degraded Service": "Perkhidmatan Separa Tergendala",
"Degraded Service": "Perkhidmatan Tergendala",
"Add Group": "Tambah Kumpulan",
"Edit Status Page": "Ubah Laman Status",
"Go to Dashboard": "Ke Dashboard",
"Status Page": "Laman Status",
"Status Pages": "Laman Status",
"Avg. Ping": "Ping Purata",
"Avg. Response": "Respons Purata",
"color": "Warna",
"Active": "Aktif",
"Inactive": "Nyahaktif",
"Blue": "Biru",
"Red": "MErah",
"Content Type": "Jenis Content",
"Home": "Laman Utama",
"Settings": "Tetapan",
"Save": "Simpan"
}

View File

@@ -659,7 +659,7 @@
"Disable": "Wyłącz",
"Date and Time": "Data i czas",
"IconUrl": "URL ikony",
"Enable DNS Cache": "Włącz pamięć podręczną DNS dla monitorów HTTP",
"Enable DNS Cache": "(Przestarzałe) Włącz pamięć podręczną DNS dla monitorów HTTP",
"Single Maintenance Window": "Pojedyncze okno konserwacji",
"Effective Date Range": "Zakres dat obowiązywania (opcjonalnie)",
"Schedule Maintenance": "Planowanie konserwacji",
@@ -838,5 +838,37 @@
"noOrBadCertificate": "Brak/zły certyfikat",
"Invert Keyword": "Odwróć słowo kluczowe",
"Expected Value": "Oczekiwana wartość",
"Json Query": "Zapytanie Json"
"Json Query": "Zapytanie Json",
"enableNSCD": "Włącz NSCD (Name Service Cache Daemon) do buforowania wszystkich żądań DNS",
"Saved.": "Zapisano.",
"setupDatabaseChooseDatabase": "Której bazy danych chcesz użyć?",
"setupDatabaseEmbeddedMariaDB": "Nie musisz niczego ustawiać. Ten obraz docker automatycznie osadził i skonfigurował MariaDB. Uptime Kuma połączy się z tą bazą danych za pośrednictwem gniazda Unix.",
"setupDatabaseMariaDB": "Połącz z zewnętrzną bazą danych MariaDB. Należy ustawić informacje o połączeniu z bazą danych.",
"setupDatabaseSQLite": "Prosty plik bazy danych, zalecany do wdrożeń na małą skalę. Przed wersją 2.0.0 Uptime Kuma używała SQLite jako domyślnej bazy danych.",
"dbName": "Nazwa bazy danych",
"toastErrorTimeout": "Limit czasu dla powiadomień o błędach",
"monitorToastMessagesLabel": "Powiadomienie Toast Monitora",
"monitorToastMessagesDescription": "Powiadomienia toast dla monitorów znikają po określonym czasie w sekundach. Ustawienie -1 wyłącza limit czasu. Ustawienie 0 wyłącza powiadomienia toast.",
"authInvalidToken": "Nieprawidłowy token.",
"authIncorrectCreds": "Nieprawidłowa nazwa użytkownika lub hasło.",
"2faAlreadyEnabled": "2FA jest już włączone.",
"2faEnabled": "2FA włączone.",
"2faDisabled": "2FA wyłączone.",
"successAdded": "Pomyślnie dodano.",
"successPaused": "Wstrzymano pomyślnie.",
"successDeleted": "Usunięto pomyślnie.",
"successEdited": "Edytowano pomyślnie.",
"successAuthChangePassword": "Hasło zostało pomyślnie zaktualizowane.",
"successDisabled": "Wyłączono pomyślnie.",
"successEnabled": "Włączono pomyślnie.",
"tagNotFound": "Nie znaleziono tagu.",
"foundChromiumVersion": "Znaleziono Chromium/Chrome. Wersja: {0}",
"authUserInactiveOrDeleted": "Użytkownik jest nieaktywny lub usunięty.",
"successResumed": "Wznowiono pomyślnie.",
"successBackupRestored": "Kopia zapasowa została pomyślnie przywrócona.",
"pushViewCode": "Jak korzystać z monitora Push? (wyświetl kod)",
"Bark API Version": "Wersja API Bark",
"pushOthers": "Inne",
"programmingLanguages": "Języki programowania",
"toastSuccessTimeout": "Limit czasu dla powiadomień o powodzeniu"
}

View File

@@ -10,7 +10,7 @@
"passwordNotMatchMsg": "A senha repetida não corresponde.",
"notificationDescription": "Atribua uma notificação ao (s) monitor (es) para que funcione.",
"keywordDescription": "Pesquise a palavra-chave em html simples ou resposta JSON e diferencia maiúsculas de minúsculas.",
"pauseDashboardHome": "Pausar",
"pauseDashboardHome": "Pausado",
"deleteMonitorMsg": "Tem certeza de que deseja excluir este monitor?",
"deleteNotificationMsg": "Tem certeza de que deseja excluir esta notificação para todos os monitores?",
"resolverserverDescription": "Cloudflare é o servidor padrão, você pode alterar o servidor resolvedor a qualquer momento.",
@@ -63,7 +63,7 @@
"Ping": "Ping",
"Monitor Type": "Tipo de Monitor",
"Keyword": "Palavra-Chave",
"Friendly Name": "Nome Amigável",
"Friendly Name": "Apelido",
"URL": "URL",
"Hostname": "Hostname",
"Port": "Porta",
@@ -196,7 +196,7 @@
"Required": "Requerido",
"webhookJsonDesc": "{0} é bom para qualquer servidor HTTP moderno, como Express.js",
"webhookAdditionalHeadersTitle": "Cabeçalhos Adicionais",
"webhookAdditionalHeadersDesc": "Define cabeçalhos adicionais enviados com o webhook.",
"webhookAdditionalHeadersDesc": "Define cabeçalhos adicionais enviados com o webhook. Cada cabeçalho deve ser definido como uma chave/valor JSON.",
"Webhook URL": "URL Do Webhook",
"Priority": "Prioridade",
"Read more": "Ver mais",
@@ -306,11 +306,11 @@
"Notification Service": "Serviço De Notificação",
"default: notify all devices": "padrão: notificar todos os dispositivos",
"Trigger type:": "Tipo Do Acionamento:",
"Then choose an action, for example switch the scene to where an RGB light is red.": "",
"Then choose an action, for example switch the scene to where an RGB light is red.": "Em seguida, escolha uma ação, por exemplo, alterar a cena onde uma luz RGB está vermelha.",
"Enable": "Habilitado",
"Disable": "Desabilitado",
"IconUrl": "URL Do Ícone",
"Enable DNS Cache": "Habilitar Cache Do DNS",
"Enable DNS Cache": "(Descontinuado) Habilitar o Cache DNS para monitores HTTP(s)",
"Single Maintenance Window": "Janela Única De Manutenção",
"dnsCacheDescription": "Pode não funcionar em alguns ambientes com IPv6, desabita caso encontre qualquer problema.",
"Messaging API": "API Da Mensageira",
@@ -588,5 +588,206 @@
"wayToGetDiscordURL": "Voce pode configurar isso indo à Configurações do Servidor -> Integrações -> Ver Webhooks -> Novo Webhook",
"Home": "Início",
"Reconnecting...": "Reconectando...",
"Cannot connect to the socket server": "Não foi possível conectar ao servidor socket"
"Cannot connect to the socket server": "Não foi possível conectar ao servidor socket",
"Uptime Kuma URL": "URL do Uptime Kuma",
"Saved.": "Salvo.",
"Feishu WebHookUrl": "URL de Webhook do Feishu",
"serwersmsAPIUser": "Nome de usuário da API (incluindo o prefixo webapi_)",
"setupDatabaseEmbeddedMariaDB": "Você não precisa configurar nada. Esta imagem Docker possui o MariaDB incorporado e configurado automaticamente para você. O Uptime Kuma se conectará a este banco de dados através do soquete Unix.",
"Select": "Selecione",
"supportTelegramChatID": "Suporte para o ID de bate-papo direto / grupo / canal do chat",
"A list of Notification Services can be found in Home Assistant under \"Developer Tools > Services\" search for \"notification\" to find your device/phone name.": "Uma lista de Serviços de Notificação pode ser encontrada no Home Assistant em \"Ferramentas de Desenvolvimento > Serviços\". Pesquise por \"notificação\" para encontrar o nome do seu dispositivo/telefone.",
"chromeExecutableAutoDetect": "Auto Detectar",
"chromeExecutableDescription": "Para os usuários do Docker, se o Chromium ainda não estiver instalado, pode demorar alguns minutos para instalar e exibir o resultado do teste. Ele ocupa 1GB de espaço em disco.",
"wayToCheckSignalURL": "Você pode checar esse link para ver como configurar um:",
"wayToGetLineChannelToken": "Primeiro acesse o {0}, crie um provedor e um canal (API de Mensagens), então você pode obter o token de acesso do canal e o ID do usuário nos itens de menu mencionados acima.",
"aboutMattermostChannelName": "Você pode substituir o canal padrão para o qual o Webhook envia postagens, inserindo o nome do canal no campo \"Nome do Canal\". Isso precisa ser habilitado nas configurações do Webhook do Mattermost. Por exemplo: #outro-canal",
"invertKeywordDescription": "Procure pela palavra-chave estar ausente em vez de presente.",
"jsonQueryDescription": "Faça uma consulta JSON na resposta e verifique o valor esperado (o valor de retorno será convertido em uma string para comparação). Confira <a href='https://jsonata.org/'>jsonata.org</a> para a documentação sobre a linguagem de consulta. Você pode encontrar um playground <a href='https://try.jsonata.org/'>aqui</a>.",
"octopushTypePremium": "Premium (Rápido - recomendado para alertas)",
"octopushTypeLowCost": "Baixo Custo (Lento - às vezes bloqueado pelo operador)",
"octopushSMSSender": "Nome do Remetente de SMS: 3-11 caracteres alfanuméricos e espaço (a-zA-Z0-9)",
"pushoversounds pianobar": "Piano Bar",
"SendKey": "\"SendKey\" é uma palavra usada para notificação do ServerChan",
"goAlertInfo": "GoAlert é uma aplicação de código aberto para escalas de plantão, escalonamentos automatizados e notificações (como SMS ou chamadas de voz). Engage automaticamente a pessoa certa, da maneira certa e no momento certo! {0}",
"promosmsTypeFlash": "SMS FLASH - A mensagem será exibida automaticamente no dispositivo do destinatário. Limitado apenas aos destinatários poloneses.",
"promosmsTypeSpeed": "SMS SPEED - Maior prioridade no sistema. Muito rápido e confiável, mas custoso (cerca de duas vezes o preço do SMS FULL).",
"matrixDesc1": "Você pode encontrar o ID da sala interna olhando na seção avançada das configurações da sala em seu cliente Matrix. Deve parecer algo como !QMdRCpUIfLwsfjxye6:home.server.",
"matrixDesc2": "É altamente recomendado que você crie um novo usuário e não use o token de acesso do seu próprio usuário Matrix, pois isso permitirá acesso total à sua conta e todas as salas às quais você se juntou. Em vez disso, crie um novo usuário e convide-o apenas para a sala na qual você deseja receber a notificação. Você pode obter o token de acesso executando {0}.",
"aboutChannelName": "Digite o nome do canal no campo Nome do Canal em {0} se você deseja ignorar o canal do Webhook. Exemplo: #outro-canal",
"wayToGetPagerDutyKey": "Você pode obter isso indo para Serviço -> Diretório de Serviço -> (Selecionar um serviço) -> Integrações -> Adicionar integração. Aqui você pode procurar por \"Events API V2\". Mais informações {0}.",
"From Name/Number": "Nome/Número de Origem",
"Server URL should not contain the nfty topic": "A URL do servidor não deve conter o tópico do nfty",
"pushDeerServerDescription": "Deixe em branco para usar o servidor oficial",
"wayToGetPagerTreeIntegrationURL": "Após criar a integração do Uptime Kuma no PagerTree, copie o Endpoint. Veja detalhes completos {0}",
"setupDatabaseChooseDatabase": "Qual banco de dados você deseja usar?",
"setupDatabaseMariaDB": "Conectar a um banco de dados MariaDB externo. Você precisa definir as informações de conexão com o banco de dados.",
"setupDatabaseSQLite": "Um arquivo de banco de dados simples, recomendado para implantações de pequena escala. Antes da versão 2.0.0, o Uptime Kuma usava o SQLite como banco de dados padrão.",
"dbName": "Nome do Banco de Dados",
"styleElapsedTimeShowNoLine": "Mostrar (Sem Linha)",
"styleElapsedTimeShowWithLine": "Mostrar (Com Linha)",
"filterActive": "Ativo",
"filterActivePaused": "Pausado",
"selectedMonitorCount": "Selecione: {0}",
"enableNSCD": "Habilitar o NSCD (Name Service Cache Daemon) para o cache de todas as solicitações DNS",
"chromeExecutable": "Executável do Chrome/Chromium",
"Edit Maintenance": "Editar Manutenção",
"aboutIconURL": "Você pode fornecer um link para uma imagem em \"URL do Ícone\" para substituir a imagem de perfil padrão. Não será usado se o \"Ícone Emoji\" estiver configurado.",
"octopushAPIKey": "\"Chave API\" das credenciais da API HTTP no painel de controle.",
"octopushLogin": "\"Login\" das credenciais da API HTTP no painel de controle.",
"pushoversounds pushover": "Pushover (padrão)",
"pushoversounds bike": "Bicicleta",
"pushoversounds bugle": "Corneta",
"pushoversounds cashregister": "Caixa registradora",
"pushoversounds classical": "Clássico",
"pushoversounds cosmic": "Cósmico",
"pushoversounds falling": "Cair",
"pushoversounds gamelan": "Gamelão",
"pushoversounds incoming": "Entrada",
"checkPrice": "Verifique os preços de {0}:",
"Check octopush prices": "Verifique os preços da octopush {0}.",
"octopushPhoneNumber": "Número de telefone (formato internacional, por exemplo: +33612345678) ",
"LunaSea Device ID": "ID do Dispositivo LunaSea",
"Apprise URL": "URL do Apprise",
"Strategy": "Estratégia",
"Free Mobile User Identifier": "Identificador de Usuário Free Mobile",
"Free Mobile API Key": "Chave da API Free Mobile",
"Enable TLS": "Habilitar TLS",
"Proto Service Name": "Nome do Serviço Proto",
"Proto Method": "Método Proto",
"Proto Content": "Conteúdo Proto",
"Economy": "Economia",
"Lowcost": "Baixo Custo",
"high": "alto",
"pushoversounds intermission": "Intervalo",
"pushoversounds magic": "Mágico",
"pushoversounds mechanical": "Mecânico",
"pushoversounds siren": "Sirene",
"pushoversounds spacealarm": "Alarme Espacial",
"pushoversounds tugboat": "Rebocador",
"pushoversounds alien": "Alarme Alienígena (longo)",
"pushoversounds climb": "Subir (longo)",
"pushoversounds persistent": "Persistente (longo)",
"pushoversounds echo": "Eco pushover (longo)",
"pushoversounds updown": "Up Down (longo)",
"SMSManager API Docs": "Docs da API SMSManager ",
"Gateway Type": "Tipo de gateway",
"You can divide numbers with": "Você pode dividir números com",
"Base URL": "URL base",
"goAlertIntegrationKeyInfo": "Obtenha a chave de integração genérica da API para o serviço neste formato \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\", geralmente o valor do parâmetro token da URL copiada.",
"AccessKeyId": "ID da Chave de Acesso",
"promosmsTypeFull": "SMS FULL - Nível premium de SMS, você pode usar o seu Nome do Remetente (é necessário registrar o nome primeiro). Confiável para alertas.",
"promosmsPhoneNumber": "Número de telefone (para destinatários poloneses, você pode pular os códigos de área)",
"promosmsSMSSender": "Nome do Remetente de SMS: Nome pré-registrado ou um dos padrões: InfoSMS, SMS Info, MaxSMS, INFO, SMS",
"matrixHomeserverURL": "URL do Servidor (com http(s):// e opcionalmente a porta)",
"Notify Channel": "Canal de Notificação",
"aboutNotifyChannel": "O canal de notificação acionará uma notificação na área de trabalho ou no dispositivo móvel para todos os membros do canal, independentemente de sua disponibilidade estar definida como ativa ou ausente.",
"signalImportant": "IMPORTANTE: Você não pode misturar grupos e números nos destinatários!",
"aboutKumaURL": "Se você deixar o campo URL do Uptime Kuma em branco, ele será definido como padrão para a página do GitHub do projeto.",
"smtpDkimDesc": "Por favor, consulte o DKIM do Nodemailer em {0} para obter instruções de uso.",
"Auto resolve or acknowledged": "Auto resolva ou reconhecido",
"auto acknowledged": "reconhecimento automático",
"auto resolve": "resolução automática",
"alertaApiEndpoint": "Endpoint da API",
"serwersmsSenderName": "Nome do Remetente de SMS (registrado via portal do cliente)",
"smseagleGroup": "Nome(s) do grupo(s) da agenda telefônica",
"smseagleContact": "Nome(s) do(s) contato(s) da agenda telefônica",
"smseagleRecipientType": "Tipo de destinatário",
"smseagleRecipient": "Destinatário(s) (múltiplos devem ser separados por vírgula)",
"smseagleUrl": "URL do seu dispositivo SMSEagle",
"Recipient Number": "Número do Destinatário",
"Leave blank to use a shared sender number.": "Deixe em branco para usar um número de remetente compartilhado.",
"Octopush API Version": "Versão da API Octopush",
"Legacy Octopush-DM": "Octopush-DM Legado",
"ntfy Topic": "Tópico do ntfy",
"onebotHttpAddress": "Endereço HTTP do OneBot",
"onebotMessageType": "Tipo de Mensagem do OneBot",
"PushDeer Server": "Servidor PushDeer",
"PushDeer Key": "Chave PushDeer",
"wayToGetClickSendSMSToken": "Você pode obter o Nome de Usuário da API e a Chave da API em {0}.",
"Custom Monitor Type": "Tipo de Monitor Personalizado",
"Body Encoding": "Codificação do Corpo",
"API Keys": "Chaves da API",
"pagertreeSilent": "Silencioso",
"pagertreeLow": "Baixo",
"pagertreeMedium": "Médio",
"pagertreeHigh": "Alto",
"pagertreeCritical": "Crítico",
"pagertreeResolve": "Resolução Automática",
"pagertreeDoNothing": "Não fazer nada",
"lunaseaTarget": "Alvo",
"lunaseaDeviceID": "ID do Dispositivo",
"lunaseaUserID": "ID do Usuário",
"ntfyAuthenticationMethod": "Método de Autenticação",
"ntfyUsernameAndPassword": "Usuário e Senha",
"twilioAccountSID": "SID da Conta",
"twilioApiKey": "Chave da API (opcional)",
"twilioAuthToken": "Token de Autenticação / Segredo da Chave da API",
"twilioFromNumber": "Número de origem",
"twilioToNumber": "Número de destino",
"Monitor Setting": "Configuração do monitor de {0}",
"Show Clickable Link Description": "Se marcada, todos que têm acesso a esta página de status podem ter acesso ao URL do monitor.",
"Group": "Grupo",
"Monitor Group": "Grupo do Monitor",
"Request Timeout": "Tempo Limite da Solicitação",
"timeoutAfter": "Tempo limite após {0} segundos",
"webhookBodyPresetOption": "Predefinição - {0}",
"webhookBodyCustomOption": "Corpo Customizado",
"Check/Uncheck": "Marcar/Desmarcar",
"tailscalePingWarning": "Para usar o monitor Tailscale Ping, você precisa instalar o Uptime Kuma sem o Docker e também instalar o cliente Tailscale em seu servidor.",
"telegramMessageThreadIDDescription": "Identificador único opcional para o tópico da mensagem alvo do fórum; apenas para supergrupos de fóruns.",
"pushoversounds none": "Nenhum (silencioso)",
"pushyAPIKey": "Chave de API Secreta",
"pushyToken": "Token do Dispositivo",
"GoogleChat": "Google Chat (Apenas para Google Workspace)",
"wayToGetKookGuildID": "Ative o 'Modo Desenvolvedor' nas configurações do Kook e clique com o botão direito do mouse no servidor para obter seu ID.",
"Guild ID": "ID do Servidor (Guild)",
"pushoverDesc1": "A prioridade de emergência (2) possui um intervalo de 30 segundos entre as tentativas padrão e expirará após 1 hora.",
"pushoverDesc2": "Se você deseja enviar notificações para diferentes dispositivos, preencha o campo \"Dispositivo\".",
"pushoverMessageTtl": "Tempo de Vida da Mensagem (Segundos)",
"pushoversounds vibrate": "Somente vibrar",
"SecretAccessKey": "Segredo da Chave de Acesso",
"PhoneNumbers": "Números de Telefone",
"TemplateCode": "Código de Modelo",
"SignName": "Nome de Assinatura",
"Sms template must contain parameters: ": "O modelo de SMS deve conter parâmetros: ",
"Bark Endpoint": "Endpoint do Bark",
"Bark Group": "Grupo do Bark",
"Bark Sound": "Som do Bark",
"WebHookUrl": "URL de Webhook",
"SecretKey": "Chave Secreta",
"High": "Alto",
"WeCom Bot Key": "Chave do Bot do WeCom",
"promosmsTypeEco": "SMS ECO - barato, mas lento e frequentemente sobrecarregado. Limitado apenas aos destinatários poloneses.",
"styleElapsedTime": "Tempo decorrido abaixo da barra de pulsação",
"Expected Value": "Valor Esperado",
"webhookCustomBodyDesc": "Defina um corpo HTTP personalizado para a solicitação. Variáveis de modelo {msg}, {heartbeat}, {monitor} são aceitas.",
"Invert Keyword": "Palavra-chave de Inversão",
"Json Query": "Consulta JSON",
"toastErrorTimeout": "Tempo limite para Notificações de Erro",
"toastSuccessTimeout": "Tempo limite para Notificações de Sucesso",
"monitorToastMessagesLabel": "Monitorar notificações Toast",
"monitorToastMessagesDescription": "As notificações Toast para monitores desaparecem após um determinado tempo em segundos. Definir como -1 desativa o tempo limite. Definir como 0 desativa as notificações Toast.",
"Open Badge Generator": "Gerador de Distintivo",
"Badge Label Color": "Cor do Nome do Distintivo",
"Badge Color": "Cor do Distintivo",
"Badge Label Prefix": "Prefixo do Nome do Distintivo",
"Badge Preview": "Prévia do Distintivo",
"Badge Label Suffix": "Sufixo do Nome do Distintivo",
"Badge Up Color": "Cor de Cima do Distintivo",
"Badge Down Color": "Cor de Baixo do Distintivo",
"Badge Pending Color": "Cor do Distintivo Pendente",
"Badge Maintenance Color": "Cor do Distintivo Em Manutenção",
"Badge Warn Color": "Cor do Distintivo de Aviso",
"Badge Warn Days": "Dias de Aviso do Distintivo",
"Badge Down Days": "Dias Desligado do Distintivo",
"Badge Style": "Estilo do Distintivo",
"Badge value (For Testing only.)": "Valor do Distintivo (Apenas para Testes).",
"Badge URL": "URL do Distintivo",
"Badge Generator": "Gerador de Distintivo de {0}",
"Badge Type": "Tipo de Distintivo",
"Badge Duration (in hours)": "Duração do Distintivo (em horas)",
"Badge Label": "Nome do Distintivo",
"Badge Prefix": "Prefixo do Valor do Distintivo",
"Badge Suffix": "Sufixo do Valor do Distintivo"
}

View File

@@ -639,7 +639,7 @@
"Server Timezone": "Часовой пояс сервера",
"statusPageMaintenanceEndDate": "Конец",
"IconUrl": "URL иконки",
"Enable DNS Cache": "Включить DNS кэш для мониторов HTTP(S)",
"Enable DNS Cache": "(Устарело) Включить DNS кэш для мониторов HTTP(S)",
"Enable": "Включить",
"Disable": "Отключить",
"Single Maintenance Window": "Единое окно техбслуживания",
@@ -847,5 +847,20 @@
"nostrRecipientsHelp": "формат npub, по одному в строке",
"FlashDuty Severity": "Серьёзность",
"nostrRelays": "Реле Nostr",
"nostrRelaysHelp": "Один URL-адрес ретрансляции в каждой строке"
"nostrRelaysHelp": "Один URL-адрес ретрансляции в каждой строке",
"enableNSCD": "Включить NSCD (Name Service Cache Daemon) для кэширования всех DNS-запросов",
"Saved.": "Сохранено",
"setupDatabaseChooseDatabase": "Какую базу данных Вы хотите использовать?",
"setupDatabaseEmbeddedMariaDB": "Вам не нужно ничего настраивать. В этот докер-образ автоматически встроена и настроена MariaDB. Uptime Kuma будет подключаться к этой базе данных через unix-сокет.",
"setupDatabaseSQLite": "Простой файл базы данных, рекомендуемый для небольших развертываний. До версии 2.0.0 Uptime Kuma использовал SQLite в качестве базы данных по умолчанию.",
"setupDatabaseMariaDB": "Подключитесь к внешней базе данных MariaDB. Необходимо задать информацию о подключении к базе данных.",
"dbName": "Имя базы данных",
"pushViewCode": "Как использовать монитор Push? (Посмотреть код)",
"programmingLanguages": "Языки программирования",
"Bark API Version": "Версия Bark API",
"monitorToastMessagesDescription": "Уведомления для мониторов исчезают через заданное время в секундах. Значение -1 отключает тайм-аут. Значение 0 отключает уведомления.",
"monitorToastMessagesLabel": "Уведомления",
"toastErrorTimeout": "Таймаут для уведомлений об ошибках",
"toastSuccessTimeout": "Таймаут для уведомлений об успехе",
"pushOthers": "Другие"
}

View File

@@ -137,5 +137,239 @@
"resendDisabled": "Omsändning inaktiverat",
"Pick Affected Monitors...": "Välj påverkade övervakare…",
"Select status pages...": "Välj statussidor…",
"General Monitor Type": "Allmänna övervakare"
"General Monitor Type": "Allmänna övervakare",
"webhookFormDataDesc": "{multipart} är bra för PHP. Den JSON kommer att bli analyserat med {decodeFunction}",
"appriseInstalled": "Apprise är installerad.",
"clearDataOlderThan": "Behåll övervakare historik data i {0} dagar.",
"steamApiKeyDescription": "För att övervaka en Steam Game Server behöver man en Steam Web-API nyckel. Du kan registrera din API nyckel här: ",
"No Monitors": "Inga Övervaktare",
"shrinkDatabaseDescription": "Utlösa databas VACUUM för SQLite. Om din databas skappades efter 1.10.0, AUTO_VACUUM är redan aktiverat och denna aktion behövs inte.",
"proxyDescription": "Proxyservrar måste tilldelas en övervakare för att fungera.",
"setAsDefaultProxyDescription": "Denna proxyserver kommer att aktiveras som standard för nya övervakare. Du kan fortfarande inaktivera proxyserven var för sig övervakare.",
"Content Type": "Innehållstyp",
"webhookAdditionalHeadersDesc": "Ställer in ytterligare headers skickat med webhooken. Varenda header skulle definieras som ett JSON nyckel/värde par.",
"RadiusCallingStationIdDescription": "Kallande Enhetsidentifierare",
"Slug": "Slugg",
"Invert Keyword": "Invertera Nyckelord",
"Degraded Service": "Försämrad Tjänst",
"Request Timeout": "Request Timeout",
"timeoutAfter": "Timeout efter {0} sekunder",
"styleElapsedTime": "Förfluten tid under den heartbeat indikatorn",
"styleElapsedTimeShowNoLine": "Visa (Utan Linje)",
"styleElapsedTimeShowWithLine": "Visa (Med Linje)",
"Create": "Skapa",
"Clear Data": "Radera Data",
"Auto Get": "Hämta Automatiskt",
"Overwrite": "Skriva över",
"Options": "Alternativ",
"Verify Token": "Verifiera Token",
"Enable 2FA": "Aktivera 2FA",
"Disable 2FA": "Inaktivera 2FA",
"2FA Settings": "2FA Inställningar",
"Two Factor Authentication": "Tvåfaktorsautentisering",
"filterActive": "Aktiv",
"filterActivePaused": "Pausad",
"Inactive": "Inaktiv",
"Setup 2FA": "Uppstart 2FA",
"Clear all statistics": "Rensa alla Statistiker",
"Skip existing": "Hoppa över existerande",
"Keep both": "Behåll båda",
"Token": "Token",
"notAvailableShort": "N/A",
"Apply on all existing monitors": "Applicera på alla existerande övervakare",
"Heartbeats": "Heartbeats",
"Show URI": "Visa URI",
"color": "Färg",
"value (optional)": "Värde (valfritt)",
"Gray": "Grå",
"Tags": "Taggar",
"Tag with this name already exist.": "Tag med detta namn finns redan.",
"Red": "Röd",
"Orange": "Orange",
"Green": "Grön",
"Blue": "Blå",
"Indigo": "Indigoblå",
"Purple": "Lila",
"Pink": "Rosa",
"Custom": "Anpassad",
"Search...": "Sök…",
"Avg. Ping": "Medelvärde Ping",
"Tag with this value already exist.": "Tagg med detta värde finns redan.",
"Avg. Response": "Medelvärde Respons",
"Entry Page": "Entry Sida",
"statusPageRefreshIn": "Uppdaterar om: {0}",
"No Services": "Inga Tjänster",
"All Systems Operational": "Alla System i Drift",
"Partially Degraded Service": "Delvis Försämrada Tjänster",
"Add Group": "Lägg till Grupp",
"Add New Tag": "Lägg till Ny Tagg",
"Add New below or Select...": "Lägg till Ny under eller Välj…",
"Add a monitor": "Lägg till en Övervakare",
"Edit Status Page": "Hantera Status Sida",
"Status Page": "Status Sida",
"Status Pages": "Status Sidor",
"Go to Dashboard": "Till Dashboard",
"here": "här",
"Required": "Krävs",
"webhookJsonDesc": "{0} är bra för någon moderna HTTP servrar till exempel Express.js",
"webhookCustomBodyDesc": "Definiera en anpassad HTTP Body till den request. Mall variabler {msg}, {heartbeat}, {monitor} accepteras.",
"webhookAdditionalHeadersTitle": "Ytterligare Headers",
"webhookBodyPresetOption": "Förinställning - {0}",
"defaultNotificationName": "Min {notification} Varna ({number})",
"webhookBodyCustomOption": "Anpassad Body",
"Webhook URL": "Webhook URL",
"Application Token": "Applikation Token",
"Server URL": "Server URL",
"Priority": "Prioritet",
"emojiCheatSheet": "Emoji fusklapp: {0}",
"Read more": "Läs mer",
"appriseNotInstalled": "Apprise är inte installerad. {0}",
"Method": "Metod",
"Body": "Body",
"Headers": "Headers",
"HeadersInvalidFormat": "Requestens headers är inte giltig JSON: ",
"BodyInvalidFormat": "Requestens body är inte giltig JSON: ",
"Monitor History": "Övervakare Historik",
"PasswordsDoNotMatch": "Lösenorden matchar inte.",
"records": "rekorder",
"One record": "En rekord",
"Current User": "Nuvarande Användare",
"topic": "Ämne",
"topicExplanation": "MQTT ämne att övervaka",
"successMessageExplanation": "MQTT meddelande som ska anses vara framgång",
"successMessage": "Framgång Meddelande",
"recent": "Nyligen",
"Done": "Klar",
"Info": "Info",
"Security": "Säkerhet",
"Steam API Key": "Steam API Nyckel",
"Shrink Database": "Minska Databas",
"Pick a RR-Type...": "Välj en RR-Typ…",
"Pick Accepted Status Codes...": "Välj Accepterade Statuskod…",
"Default": "Standard",
"HTTP Options": "HTTP Alternativ",
"Create Incident": "Skapa Incident",
"Title": "Titel",
"Content": "Innehåll",
"Style": "Stil",
"info": "info",
"Post URL": "Post URL",
"PushUrl": "Push URL",
"danger": "risk",
"error": "fel",
"critical": "kritisk",
"primary": "primär",
"light": "ljus",
"dark": "mörk",
"Post": "Post",
"Please input title and content": "Snälla lägg till titel och innehåll",
"Created": "Skapat",
"Last Updated": "Senaste Uppdaterade",
"Switch to Light Theme": "Byt till Ljustema",
"Switch to Dark Theme": "Byt till Mörktema",
"Show Tags": "Visa Taggar",
"Hide Tags": "Göma Taggar",
"Description": "Beskrivning",
"No monitors available.": "Inga övervaktare tillgänglig.",
"Add one": "Lägg till en",
"Untitled Group": "Namnlös Grupp",
"Services": "Tjänster",
"Discard": "Radera",
"Cancel": "Kancellera",
"Select": "Välj",
"selectedMonitorCount": "Valt: {0}",
"Check/Uncheck": "Markera/Omarkera",
"Powered by": "Drivs av",
"Customize": "Anpassa",
"Custom Footer": "Anpassad Footer",
"Custom CSS": "Anpassad CSS",
"deleteStatusPageMsg": "Är du säkert att du vill radera denna status sida?",
"Proxies": "Proxyservrar",
"default": "Standard",
"enabled": "Aktiverad",
"setAsDefault": "Ange Som Standard",
"deleteProxyMsg": "Är du säkert att du vill radera denna proxyserver för alla övervakare?",
"Certificate Chain": "Certifikatkedja",
"Valid": "Giltig",
"Invalid": "Ogiltig",
"User": "Användare",
"Installed": "Installerat",
"Not installed": "Installerat ej",
"Running": "Körs",
"Not running": "Körs inte",
"Remove Token": "Ta bort Token",
"Start": "Starta",
"Stop": "Stoppa",
"Add New Status Page": "Lägg till Ny Status Sida",
"Accept characters:": "Acceptera tecken:",
"Unpin": "Unpin",
"startOrEndWithOnly": "Börja eller sluta med {0} bara",
"No consecutive dashes": "Ingen följande streck",
"Next": "Nästa",
"The slug is already taken. Please choose another slug.": "Sluggen är redan tagit. Snälla välja en annan slugg.",
"No Proxy": "Ingen Proxyservern",
"Authentication": "Autentisering",
"HTTP Basic Auth": "HTTP Basic Auth",
"New Status Page": "Ny Status Sida",
"Page Not Found": "Sida hittas inte",
"Reverse Proxy": "Omvänd Proxyserver",
"Backup": "Backup",
"About": "Om",
"wayToGetCloudflaredURL": "(Ladda ned cloudflared från {0})",
"cloudflareWebsite": "Cloudflare Webbsida",
"Message:": "Meddelande:",
"Don't know how to get the token? Please read the guide:": "Vet inte hur att få en token? Snälla läs guiden:",
"The current connection may be lost if you are currently connecting via Cloudflare Tunnel. Are you sure want to stop it? Type your current password to confirm it.": "Den nuvarande anslutningen kan kopplas bort om du närvarande ansluter via Cloudflare Tunnel. Är du säkert du vill avsluta den? Skriv ditt nuvarande lösenord för att konfirmera.",
"HTTP Headers": "HTTP Headers",
"Trust Proxy": "Lita på Proxyserver",
"Other Software": "Annan programvara",
"For example: nginx, Apache and Traefik.": "Till exempel: nginx, Apache och Traefik.",
"Please read": "Läs är du snäll",
"Subject:": "Subjekt:",
"Valid To:": "Giltig Till:",
"Days Remaining:": "Dagar kvar:",
"Fingerprint:": "Fingertryck:",
"No status pages": "Ingen status sidor",
"Proxy": "Proxyservern",
"Date Created": "Datum Skapade",
"Footer Text": "Footer Text",
"Show Powered By": "Visa Drivs Av",
"Domain Names": "Domain Namn",
"signedInDisp": "Inloggad som {0}",
"signedInDispDisabled": "Auth Inaktiverad.",
"RadiusSecret": "Radius Hemlighet",
"RadiusSecretDescription": "Delad Hemlighet mellan client och server",
"RadiusCalledStationId": "Kancellerad Station Id",
"RadiusCalledStationIdDescription": "Enhetsidentifierare",
"RadiusCallingStationId": "Calling Station Id",
"Certificate Expiry Notification": "Notif om certifikatets utgång",
"Domain Name Expiry Notification": "Notif om Domain Namns utgång",
"API Key": "API Nyckel",
"API Username": "API Användarnamn",
"Show update if available": "Visa uppdatering om tillgänglig",
"Also check beta release": "Kolla upp också beta version",
"Events": "Händelser",
"Active": "Aktiv",
"statusPageNothing": "Ingenting här, snälla lägg till en grupp eller en monitor.",
"warning": "varning",
"Issuer:": "Utfärdare:",
"Expected Value": "Förväntat Värde",
"Primary Base URL": "Huvud Bas URL",
"Home": "Hem",
"Cannot connect to the socket server": "Kan inte koppla till socketservern",
"Reconnecting...": "Återanslutar...",
"Json Query": "Json Query",
"Default enabled": "Standard aktiverad",
"pushViewCode": "Visa kod",
"Steam Game Server": "Steam Spel Server",
"Docker Container": "Docker Container",
"setupDatabaseChooseDatabase": "Vilken databas skulle du vilja använda?",
"dbName": "Databas Namn",
"What you can try:": "Vad du kan försöka:",
"Container Name / ID": "Containernamn / ID",
"Docker Host": "Docker värd",
"Docker Hosts": "Docker värdar",
"Domain": "Domän",
"Most likely causes:": "Störst troliga anledningar:",
"Coming Soon": "Kommer snart"
}

View File

@@ -187,5 +187,127 @@
"Tag with this value already exist.": "ఈ విలువతో ట్యాగ్ ఇప్పటికే ఉంది.",
"Custom": "కస్టమ్",
"Entry Page": "ఎంట్రీ పేజీ",
"statusPageNothing": "ఇక్కడ ఏమీ లేదు, దయచేసి సమూహాన్ని లేదా మానిటర్‌ని జోడించండి."
"statusPageNothing": "ఇక్కడ ఏమీ లేదు, దయచేసి సమూహాన్ని లేదా మానిటర్‌ని జోడించండి.",
"No Services": "సేవలు లేవు",
"Partially Degraded Service": "పాక్షికంగా క్షీణించిన సేవ",
"Degraded Service": "దిగజారిన సేవ",
"Add Group": "సమూహాన్ని జోడించండి",
"Add a monitor": "మానిటర్‌ను జోడించండి",
"Go to Dashboard": "డాష్బోర్డ్ కు వెళ్ళండి",
"Status Page": "స్థితి పేజీ",
"Status Pages": "స్థితి పేజీలు",
"here": "ఇక్కడ",
"Required": "అవసరం",
"Post URL": "పోస్ట్ URL",
"Content Type": "కంటెంట్ రకం",
"webhookFormDataDesc": "PHPకి {multipart} మంచిది. JSON {decodeFunction}తో అన్వయించబడాలి",
"webhookAdditionalHeadersTitle": "అదనపు శీర్షికలు",
"webhookBodyPresetOption": "ప్రీసెట్ - {0}",
"webhookBodyCustomOption": "కస్టమ్ బాడీ",
"Webhook URL": "వెబ్‌హుక్ URL",
"Application Token": "అప్లికేషన్ టోకెన్",
"Server URL": "సర్వర్ URL",
"Priority": "ప్రాధాన్యత",
"Read more": "ఇంకా చదవండి",
"appriseInstalled": "అప్రైజ్ ఇన్‌స్టాల్ చేయబడింది.",
"Method": "పద్ధతి",
"Body": "శరీరం",
"Headers": "హెడర్సు",
"PushUrl": "పుష్ URL",
"BodyInvalidFormat": "అభ్యర్థన విషయం JSON చెల్లదు: ",
"Monitor History": "మానిటర్ చరిత్ర",
"clearDataOlderThan": "మానిటర్ చరిత్ర డేటాను {0} రోజుల పాటు ఉంచండి.",
"records": "రికార్డులు",
"One record": "ఒక రికార్డు",
"Current User": "ప్రస్తుత వినియోగదారుడు",
"topic": "అంశం",
"topicExplanation": "పర్యవేక్షించడానికి MQTT అంశం",
"successMessage": "విజయ సందేశం",
"successMessageExplanation": "MQTT సందేశం విజయంగా పరిగణించబడుతుంది",
"recent": "ఇటీవలి",
"Done": "పూర్తి",
"Info": "సమాచారం",
"Steam API Key": "స్టీమ్ API కీ",
"Shrink Database": "డేటాబేస్ కుదించు",
"Pick Accepted Status Codes...": "ఆమోదించబడిన స్థితి కోడ్‌లను ఎంచుకోండి…",
"Default": "డిఫాల్ట్",
"HTTP Options": "HTTP ఎంపికలు",
"Title": "శీర్షిక",
"Content": "విషయము",
"Style": "శైలి",
"info": "సమాచారం",
"warning": "హెచ్చరిక",
"danger": "ప్రమాదం",
"error": "లోపం",
"primary": "ప్రాథమిక",
"light": "వెలుతురు",
"dark": "చీకటి",
"Post": "పోస్ట్",
"Created": "సృష్టించబడింది",
"Last Updated": "చివరిగా నవీకరించబడింది",
"Unpin": "అన్‌పిన్",
"Show Tags": "ట్యాగ్‌లను చూపించు",
"Hide Tags": "ట్యాగ్‌లను దాచండి",
"Description": "వివరణ",
"Add one": "ఒకటి జోడించండి",
"No Monitors": "మానిటర్లు లేవు",
"Services": "సేవలు",
"Select": "ఎంచుకోండి",
"selectedMonitorCount": "ఎంచుకున్నది: {0}",
"Powered by": "ద్వారా ఆధారితం",
"Customize": "అనుకూలీకరించండి",
"Custom Footer": "అనుకూల ఫుటర్",
"Custom CSS": "అనుకూల CSS",
"deleteStatusPageMsg": "మీరు ఖచ్చితంగా ఈ స్థితి పేజీని తొలగించాలనుకుంటున్నారా?",
"Proxies": "ప్రాక్సీలు",
"default": "డిఫాల్ట్",
"enabled": "ప్రారంభించబడింది",
"Certificate Chain": "సర్టిఫికేట్ చైన్",
"Valid": "చెల్లుబాటు అవుతుంది",
"Invalid": "చెల్లదు",
"User": "వినియోగదారు",
"Installed": "ఇన్‌స్టాల్ చేయబడింది",
"Not installed": "ఇన్‌స్టాల్ చేయలేదు",
"Running": "నడుస్తోంది",
"Not running": "నడవడం లేదు",
"Remove Token": "టోకెన్‌ని తీసివేయండి",
"Start": "ప్రారంభించండి",
"Stop": "ఆపు",
"Add New Status Page": "కొత్త స్థితి పేజీని జోడించండి",
"Slug": "స్లగ్",
"startOrEndWithOnly": "{0}తో మాత్రమే ప్రారంభించండి లేదా ముగించండి",
"Next": "తరువాత",
"No Proxy": "ప్రాక్సీ లేదు",
"All Systems Operational": "అన్ని సిస్టమ్స్ ఆపరేషనల్",
"Edit Status Page": "స్థితి పేజీని సవరించండి",
"defaultNotificationName": "నా {నోటిఫికేషన్} హెచ్చరిక ({సంఖ్య})",
"webhookJsonDesc": "Express.js వంటి ఏదైనా ఆధునిక HTTP సర్వర్‌లకు {0} మంచిది",
"webhookCustomBodyDesc": "అభ్యర్థన కోసం అనుకూల HTTP బాడీని నిర్వచించండి. టెంప్లేట్ వేరియబుల్స్ {msg}, {heartbeat}, {monitor} ఆమోదయోగ్యమైనవి.",
"webhookAdditionalHeadersDesc": "webhookతో పంపబడిన అదనపు హెడర్లను సెట్ చేస్తుంది. ప్రతి హెడర్ JSON కీ/విలువగా నిర్వచించబడాలి.",
"emojiCheatSheet": "ఎమోజి చీట్ షీట్: {0}",
"appriseNotInstalled": "అప్రైజ్ ఇన్‌స్టాల్ చేయబడలేదు. {0}",
"HeadersInvalidFormat": "అభ్యర్థన హెడర్సు చెల్లుబాటు కావు JSON: ",
"PasswordsDoNotMatch": "గుత్త పదములు సరి పోవట్లేదు.",
"steamApiKeyDescription": "స్టీమ్ గేమ్ సర్వర్‌ని పర్యవేక్షించడానికి మీకు స్టీమ్ వెబ్-API కీ అవసరం. మీరు మీ API కీని ఇక్కడ నమోదు చేసుకోవచ్చు: ",
"Security": "భద్రత",
"Pick a RR-Type...": "RR-రకాన్ని ఎంచుకోండి…",
"Create Incident": "సంఘటనను సృష్టించండి",
"critical": "ప్రమాదకరమైన",
"Please input title and content": "దయచేసి శీర్షిక మరియు కంటెంట్‌ని ఇన్‌పుట్ చేయండి",
"Switch to Light Theme": "లైట్ థీమ్‌కి మారండి",
"Switch to Dark Theme": "డార్క్ థీమ్‌కి మారండి",
"No monitors available.": "మానిటర్లు అందుబాటులో లేవు.",
"Untitled Group": "పేరులేని సమూహం",
"Discard": "విస్మరించండి",
"Cancel": "రద్దు చేయండి",
"Check/Uncheck": "చెక్/చెక్చేయవద్దు",
"shrinkDatabaseDescription": "SQLite కోసం డేటాబేస్ VACUUMని ట్రిగ్గర్ చేయండి. మీ డేటాబేస్ 1.10.0 తర్వాత సృష్టించబడితే, AUTO_VACUUM ఇప్పటికే ప్రారంభించబడింది మరియు ఈ చర్య అవసరం లేదు.",
"setAsDefault": "డిఫాల్ట్ సెట్ చేయబడింది",
"deleteProxyMsg": "మీరు ఖచ్చితంగా అన్ని మానిటర్‌ల కోసం ఈ ప్రాక్సీని తొలగించాలనుకుంటున్నారా?",
"proxyDescription": "పనిచేయడానికి ప్రాక్సీలు తప్పనిసరిగా మానిటర్‌కు కేటాయించబడాలి.",
"enableProxyDescription": "ఈ ప్రాక్సీ సక్రియం చేయబడే వరకు మానిటర్ అభ్యర్థనలపై ప్రభావం చూపదు. మీరు యాక్టివేషన్ స్థితి ద్వారా అన్ని మానిటర్‌ల నుండి ప్రాక్సీని తాత్కాలికంగా నిలిపివేయడాన్ని నియంత్రించవచ్చు.",
"setAsDefaultProxyDescription": "కొత్త మానిటర్‌ల కోసం ఈ ప్రాక్సీ డిఫాల్ట్‌గా ప్రారంభించబడుతుంది. మీరు ఇప్పటికీ ప్రతి మానిటర్‌కు విడిగా ప్రాక్సీని నిలిపివేయవచ్చు.",
"Accept characters:": "అక్షరాలను అంగీకరించండి:",
"No consecutive dashes": "వరుస డాష్‌లను ఉపయోగించవద్దు",
"The slug is already taken. Please choose another slug.": "స్లగ్ ఇప్పటికే తీసుకోబడింది. దయచేసి మరొక స్లగ్‌ని ఎంచుకోండి."
}

View File

@@ -623,7 +623,7 @@
"maintenanceStatus-scheduled": "กำหนดการ",
"maintenanceStatus-ended": "สิ้นสุด",
"maintenanceStatus-unknown": "ไม่ทราบ",
"Specific Monitor Type": "ประเภทมอนิเตอร์เฉพาะ",
"Specific Monitor Type": "ชนิดมอนิเตอร์เฉพาะ",
"telegramMessageThreadID": "(ตัวเลือก) ไอดีเทรดข้อความ",
"telegramMessageThreadIDDescription": "ตัวระบุที่ไม่ซ้ำซึ่งเป็นทางเลือกสำหรับเธรดข้อความเป้าหมาย (หัวข้อ) ของฟอรัม สำหรับฟอรัมซูเปอร์กรุ๊ปเท่านั้น",
"sameAsServerTimezone": "เช่นเดียวกับเขตเวลาของเซิร์ฟเวอร์",
@@ -675,5 +675,11 @@
"notificationRegional": "ภูมิภาค",
"timeoutAfter": "หมดเวลาหลังจาก {0} วินาที",
"Select": "เลือก",
"Expected Value": "ค่าที่คาดหวัง"
"Expected Value": "ค่าที่คาดหวัง",
"setupDatabaseChooseDatabase": "ฐานข้อมูลไหนที่ต้องการใช้งาน?",
"setupDatabaseEmbeddedMariaDB": "คุณไม่จำเป็นต้องทำอะไร Docker image จะสร้างและตั่งค่า MariaDB ให้โดยอัตโนมัติ Uptime Kuma จะเชื่อมต่อกับฐานข้อมูลนี้ด้วย unix socket",
"setupDatabaseMariaDB": "เชื่อมต่อไปยัง MariaDB ภายนอก คุณจำเป็นจะต้องตั่งค่าการเชื่อมต่อฐานข้อมูล",
"setupDatabaseSQLite": "ไฟล์ฐานข้อมูลอย่างง่าย แนะนำสำหรับการปรับใช้ขนาดเล็ก ก่อนเวอร์ชัน 2.0.0 Uptime Kuma ใช้ SQLite เป็นฐานข้อมูลเริ่มต้น",
"dbName": "ชื่อฐานข้อมูล",
"Passive Monitor Type": "ชนิดมอนิเตอร์แบบพาสซีฟ"
}

View File

@@ -640,7 +640,7 @@
"Server Timezone": "Sunucu Saat Dilimi",
"statusPageMaintenanceEndDate": "Bitiş Zamanı",
"IconUrl": "Icon URL",
"Enable DNS Cache": "HTTP monitörleri için DNS Önbelleğini etkinleştir",
"Enable DNS Cache": "(Kullanımdan kaldırıldı) HTTP(ler) monitörleri için DNS Önbelleğini etkinleştirin",
"Enable": "Etkin",
"Disable": "Devre Dışı",
"dnsCacheDescription": "Bazı IPv6 ortamlarında çalışmıyor olabilir, herhangi bir sorunla karşılaşırsanız devre dışı bırakın.",
@@ -838,5 +838,37 @@
"gamedigGuessPortDescription": "Valve Server Sorgu Protokolü tarafından kullanılan bağlantı noktası, istemci bağlantı noktasından farklı olabilir. Monitör sunucunuza bağlanamıyorsa bunu deneyin.",
"styleElapsedTimeShowNoLine": "Göster (Satır Yok)",
"styleElapsedTime": "Kalp atışı çubuğunun altında geçen süre",
"styleElapsedTimeShowWithLine": "Göster (Satır ile birlikte)"
"styleElapsedTimeShowWithLine": "Göster (Satır ile birlikte)",
"enableNSCD": "Tüm DNS isteklerini önbelleğe almak için NSCD'yi (Ad Hizmeti Önbellek Programı) etkinleştirin",
"setupDatabaseEmbeddedMariaDB": "Hiçbir şey ayarlamanıza gerek yok. Bu docker imajı sizin için otomatik olarak bir MariaDB yerleştirdi ve yapılandırdı. Çalışma Süresi Kuma bu veritabanına unix soketi aracılığıyla bağlanacaktır.",
"setupDatabaseSQLite": "Küçük ölçekli dağıtımlar için önerilen basit bir veritabanı dosyası. v2.0.0'dan önce Uptime Kuma, varsayılan veritabanı olarak SQLite'ı kullanıyordu.",
"setupDatabaseChooseDatabase": "Hangi veritabanını kullanmak istiyorsunuz?",
"setupDatabaseMariaDB": "Harici bir MariaDB veritabanına bağlanın. Veritabanı bağlantı bilgilerini ayarlamanız gerekir.",
"dbName": "Veritabanı ismi",
"Saved.": "Kaydedildi.",
"toastErrorTimeout": "Hata Bildirimleri için Zaman Aşımı",
"toastSuccessTimeout": "Başarı Bildirimleri için Zaman Aşımı",
"monitorToastMessagesLabel": "Toast bildirimlerini izleyin",
"monitorToastMessagesDescription": "Monitörler için bildirimler, saniye cinsinden belirli bir süre sonunda kaybolur. -1'e ayarlamak zaman aşımını devre dışı bırakır. 0'a ayarlamak, tost bildirimlerini devre dışı bırakır.",
"Bark API Version": "Bark API Sürümü",
"pushViewCode": "Push monitör nasıl kullanılır? (Kodu Görüntüle)",
"programmingLanguages": "Programlama dilleri",
"pushOthers": "Diğerleri",
"authInvalidToken": "Geçersiz Token.",
"authIncorrectCreds": "Kullanıcı adı ya da parola yanlış.",
"2faAlreadyEnabled": "2FA zaten etkin.",
"2faEnabled": "2FA Etkin.",
"2faDisabled": "2FA Devre Dışı.",
"successResumed": "Başarıyla Devam Edildi.",
"successPaused": "Başarıyla Duraklatıldı.",
"successDeleted": "Başarıyla silindi.",
"successEdited": "Başarıyla Düzenlendi.",
"successBackupRestored": "Yedekleme başarıyla geri yüklendi.",
"successDisabled": "Başarıyla Devre Dışı Bırakıldı.",
"successEnabled": "Başarıyla Etkinleştirildi.",
"tagNotFound": "Etiket bulunamadı.",
"authUserInactiveOrDeleted": "Kullanıcı etkin değil veya silinmiş.",
"successAdded": "Başarıyla eklendi.",
"successAuthChangePassword": "Şifre başarıyla güncellendi.",
"foundChromiumVersion": "Chromium/Chrome bulundu. Versiyon: {0}"
}

View File

@@ -678,7 +678,7 @@
"Server Timezone": "Часовий пояс сервера",
"statusPageMaintenanceEndDate": "Закінчення",
"IconUrl": "URL-адреса іконки",
"Enable DNS Cache": "Увімкнути DNS-кеш для HTTP(s) моніторів",
"Enable DNS Cache": "(Застаріле) Увімкнути DNS-кеш для HTTP(s) моніторів",
"Enable": "Увімкнути",
"confirmDeleteTagMsg": "Ви впевнені, що хочете видалити цей тег? Монітори, пов'язані з цим тегом, не будуть видалені.",
"Guild ID": "ID гільдії",
@@ -844,5 +844,37 @@
"gamedigGuessPort": "Gamedig: Вгадати порт",
"gamedigGuessPortDescription": "Порт, що використовується протоколом запитів до сервера Valve, може відрізнятися від порту клієнта. Спробуйте це, якщо монітор не може підключитися до вашого сервера.",
"styleElapsedTimeShowWithLine": "Показати (з лінією)",
"styleElapsedTimeShowNoLine": "Показати (без лінії)"
"styleElapsedTimeShowNoLine": "Показати (без лінії)",
"enableNSCD": "Увімкнути NSCD (Name Service Cache Daemon) для кешування всіх DNS-запитів",
"setupDatabaseChooseDatabase": "Яку базу даних ви хочете використовувати?",
"setupDatabaseEmbeddedMariaDB": "Вам не потрібно нічого налаштовувати. Цей докер-образ містить вбудовану та автоматично налаштовану базу даних MariaDB. Uptime Kuma підключиться до цієї бази даних через unix-сокет.",
"setupDatabaseSQLite": "Простий файл бази даних, рекомендований для невеликих проєктів. До версії 2.0.0 Uptime Kuma використовувала SQLite як базу даних за замовчуванням.",
"setupDatabaseMariaDB": "Підключитися до зовнішньої бази даних MariaDB. Вам потрібно задати інформацію для підключення до бази даних.",
"dbName": "Назва бази даних",
"Saved.": "Збережено.",
"monitorToastMessagesLabel": "Тост-сповіщення монітора",
"toastErrorTimeout": "Таймаут для сповіщень про помилки",
"toastSuccessTimeout": "Таймаут для сповіщень про успіх",
"monitorToastMessagesDescription": "Тост-сповіщення для моніторів зникають через заданий час у секундах. Значення -1 вимикає таймаут. Значення 0 вимикає тост-сповіщення.",
"Bark API Version": "Версія Bark API",
"pushViewCode": "Як користуватися Push-монітором? (Переглянути код)",
"pushOthers": "Інші",
"programmingLanguages": "Мови програмування",
"authInvalidToken": "Недійсний токен.",
"2faAlreadyEnabled": "2FA вже увімкнено.",
"2faEnabled": "2FA увімкнено.",
"2faDisabled": "2FA вимкнено.",
"successAdded": "Успішно додано.",
"successResumed": "Успішно відновлено.",
"successPaused": "Успішно зупинено.",
"successDeleted": "Успішно видалено.",
"successAuthChangePassword": "Пароль успішно оновлено.",
"successBackupRestored": "Резервну копію успішно відновлено.",
"successDisabled": "Успішно вимкнено.",
"tagNotFound": "Тег не знайдено.",
"foundChromiumVersion": "Знайдено Chromium/Chrome. Версія: {0}",
"authUserInactiveOrDeleted": "Користувач неактивний або видалений.",
"authIncorrectCreds": "Неправильне ім'я користувача або пароль.",
"successEdited": "Успішно відредаговано.",
"successEnabled": "Успішно увімкнено."
}

View File

@@ -654,7 +654,7 @@
"Server Timezone": "服务器时区",
"statusPageMaintenanceEndDate": "结束时间",
"IconUrl": "图标 URL",
"Enable DNS Cache": "为 HTTP(s) 监控项启用 DNS 缓存",
"Enable DNS Cache": "(已弃用)为 HTTP(s) 监控项启用 DNS 缓存",
"Enable": "启用",
"Disable": "禁用",
"dnsCacheDescription": "可能无法在某些 IPv6 环境工作,如果遇到问题请禁用。",
@@ -840,5 +840,37 @@
"gamedigGuessPort": "Gamedig: 自动检测端口号",
"styleElapsedTimeShowWithLine": "显示(带连接线)",
"styleElapsedTimeShowNoLine": "显示(不带连接线)",
"styleElapsedTime": "在监控项详情的心跳栏下显示起止时间"
"styleElapsedTime": "在监控项详情的心跳栏下显示起止时间",
"enableNSCD": "启用 NSCD名称服务缓存以缓存所有 DNS 请求",
"setupDatabaseChooseDatabase": "您想用哪种数据库?",
"setupDatabaseEmbeddedMariaDB": "您无需设置此项。通过 Docker 方式安装时已自动配置了一个 MariaDB 数据库。Uptime Kuma 会通过 Unix 套接字方式连接该数据库。",
"setupDatabaseMariaDB": "连接到外部 MariaDB 数据库。您需要设置该数据库的连接方式。",
"setupDatabaseSQLite": "一个简单的数据库文件,推荐小规模部署使用。在 v2.0.0 版本之前Uptime Kuma 使用 SQLite 作为默认数据库。",
"dbName": "数据库名称",
"Saved.": "已保存。",
"monitorToastMessagesLabel": "监控项的弹窗通知",
"toastSuccessTimeout": "成功类弹窗通知的自动关闭用时",
"toastErrorTimeout": "失败类弹窗通知的自动关闭用时",
"monitorToastMessagesDescription": "监控项的弹窗通知的自动关闭用时,以秒为单位。设置为 -1 将禁用弹窗通知的自动关闭功能,设置为 0 将完全禁用弹窗通知功能。",
"Bark API Version": "Bark API 版本",
"programmingLanguages": "编程语言",
"pushOthers": "其他",
"pushViewCode": "如何使用 Push 监控项?(查看示例代码)",
"authInvalidToken": "无效的令牌。",
"authUserInactiveOrDeleted": "该用户被禁用或删除。",
"tagNotFound": "标签未找到。",
"successEnabled": "已成功启用。",
"successDisabled": "已成功禁用。",
"successBackupRestored": "已成功恢复备份。",
"successEdited": "已成功编辑。",
"successDeleted": "已成功删除。",
"successPaused": "已成功暂停。",
"successResumed": "已成功恢复。",
"successAdded": "已成功添加。",
"2faDisabled": "已成功禁用 2FA。",
"2faEnabled": "已成功启用 2FA。",
"2faAlreadyEnabled": "2FA 已经启用。",
"foundChromiumVersion": "已找到 Chromium/Chrome。版本{0}",
"successAuthChangePassword": "已成功更新密码。",
"authIncorrectCreds": "错误的用户名或密码。"
}

View File

@@ -730,5 +730,28 @@
"Invert Keyword": "以上關鍵字不能出現",
"Home": "首頁",
"Expected Value": "預期值",
"Json Query": "JSON 查询"
"Json Query": "JSON 查询",
"Saved.": "已儲存。",
"Select": "選擇",
"selectedMonitorCount": "已選:{0}",
"Check/Uncheck": "選取中/取消選取",
"telegramMessageThreadIDDescription": "(可選) Telegram 在超級群組使用的話題標識字串",
"pushViewCode": "查看代碼",
"pushOthers": "其他",
"webhookBodyCustomOption": "自定義內容",
"tailscalePingWarning": "如要使用 Tailscale Ping ,您需要以非 docker 方式安裝 Uptime Kuma並在系統安裝 Tailscale 客戶端。",
"invertKeywordDescription": "出現關鍵詞將令檢測結果設為失敗,而非成功。",
"enableNSCD": "啟用 NSCD名稱服務緩存以緩存所有 DNS 請求",
"setupDatabaseChooseDatabase": "你想使用以下哪種資料庫?",
"setupDatabaseEmbeddedMariaDB": "你無需作任何設定。Docker image 中已包含設定好的 MariaDB。Uptime Kuma 會自動通過 Unix socket 連接到此資料庫。",
"setupDatabaseMariaDB": "連接到額外 MariaDB 資料庫。 你需要設置資料庫連接資訊。",
"setupDatabaseSQLite": "以一個檔案作為資料庫,建議用於小型的部署。在 v2.0.0 版本之前Uptime Kuma 使用 SQLite 作為預設資料庫。",
"dbName": "資料庫名稱",
"webhookBodyPresetOption": "預設 - {0}",
"programmingLanguages": "編程語言",
"styleElapsedTime": "狀態條下顯示已過的時間",
"styleElapsedTimeShowNoLine": "顯示(不帶連接線)",
"styleElapsedTimeShowWithLine": "顯示(帶連接線)",
"Request Timeout": "請求超時",
"timeoutAfter": "{0} 秒後為超時"
}

View File

@@ -121,7 +121,7 @@
"Enable Auth": "啟用驗證",
"disableauth.message1": ">你是否要<strong>取消登入驗證</strong>",
"disableauth.message2": "此功能是設計給已有<strong>第三方認證</strong>的使用者,例如 Cloudflare Access。",
"Please use this option carefully!": "請謹慎使用",
"Please use this option carefully!": "請謹慎使用",
"Logout": "登出",
"Leave": "離開",
"I understand, please disable": "我了解了,請停用",
@@ -220,9 +220,9 @@
"Post URL": "Post 網址",
"Content Type": "內容類型",
"webhookJsonDesc": "{0} 適合任何現代的 HTTP 伺服器,如 Express.js",
"webhookFormDataDesc": "{multipart} 適合 PHP。 JSON 必須先經由 {decodeFunction} 剖析",
"webhookFormDataDesc": "{multipart} 適合 PHP。 JSON 必須先經由 {decodeFunction} 剖析",
"webhookAdditionalHeadersTitle": "額外標頭",
"webhookAdditionalHeadersDesc": "設定與 webhook 一同傳送的額外標頭。",
"webhookAdditionalHeadersDesc": "設 webhook 請求的額外 Header。每一個 Header 應被定義為一對 JSON 鍵值對。",
"smtp": "Email (SMTP)",
"secureOptionNone": "無 / STARTTLS (25, 587)",
"secureOptionTLS": "TLS (465)",
@@ -323,14 +323,14 @@
"Body": "主體",
"Headers": "標頭",
"PushUrl": "Push 網址",
"HeadersInvalidFormat": "要求標頭不是有效的 JSON",
"BodyInvalidFormat": "要求主體不是有效的 JSON",
"HeadersInvalidFormat": "要求標頭不是有效的 JSON: ",
"BodyInvalidFormat": "要求主體不是有效的 JSON: ",
"Monitor History": "監測器歷史紀錄",
"clearDataOlderThan": "保留 {0} 天內的監測器歷史紀錄。",
"PasswordsDoNotMatch": "密碼不相符。",
"records": "記錄",
"One record": "一項記錄",
"steamApiKeyDescription": "若要監測 Steam 遊戲伺服器,您將需要 Steam Web-API 金鑰。您可以在此註冊您的 API 金鑰",
"steamApiKeyDescription": "若要監測 Steam 遊戲伺服器,您將需要 Steam Web-API 金鑰。您可以在此註冊您的 API 金鑰: ",
"Current User": "目前使用者",
"topic": "Topic",
"topicExplanation": "要監測的 MQTT Topic",
@@ -436,7 +436,7 @@
"PhoneNumbers": "PhoneNumbers",
"TemplateCode": "TemplateCode",
"SignName": "SignName",
"Sms template must contain parameters: ": "Sms 範本必須包含參數",
"Sms template must contain parameters: ": "Sms 範本必須包含參數: ",
"Bark Endpoint": "Bark 端點",
"Bark Group": "Bark 群組",
"Bark Sound": "Bark 鈴聲",
@@ -590,7 +590,7 @@
"Examples": "範例",
"Home Assistant URL": "Home Assistant 網址",
"Long-Lived Access Token": "長期有效存取權杖",
"Long-Lived Access Token can be created by clicking on your profile name (bottom left) and scrolling to the bottom then click Create Token. ": "若要建立長期有效存取權杖,請點擊您的個人檔案名稱 (左下角),捲動至最下方,然後點擊建立權杖。",
"Long-Lived Access Token can be created by clicking on your profile name (bottom left) and scrolling to the bottom then click Create Token. ": "若要建立長期有效存取權杖,請點擊您的個人檔案名稱 (左下角),捲動至最下方,然後點擊建立權杖。 ",
"Notification Service": "通知服務",
"default: notify all devices": "預設:通知所有服務",
"A list of Notification Services can be found in Home Assistant under \"Developer Tools > Services\" search for \"notification\" to find your device/phone name.": "您可以在 Home Assistant 中查看通知服務的列表,在\"開發者工具 > 服務\"下搜尋\"通知\"來找到您的裝置/手機的名稱。",
@@ -644,7 +644,7 @@
"Server Timezone": "伺服器時區",
"statusPageMaintenanceEndDate": "結束",
"IconUrl": "圖示網址",
"Enable DNS Cache": "啟用 DNS 快取",
"Enable DNS Cache": "(已棄用)為 HTTP(s) 監控項啟用 DNS 緩存",
"Enable": "啟用",
"Disable": "停用",
"dnsCacheDescription": "在某些 IPv6 環境可能會無法運作,如果您遇到任何問題,請停用。",
@@ -689,7 +689,7 @@
"telegramProtectContentDescription": "如果啟用Telegram 中的機器人訊息將受到保護,不會被轉發和保存。",
"installing": "安裝中",
"uninstall": "卸載",
"loadingError": "無法獲取數據, 請重試",
"loadingError": "無法獲取數據, 請重試",
"markdownSupported": "支援 Markdown 語法",
"Packet Size": "數據包大小",
"statusPageRefreshIn": "將於 {0} 後刷新",
@@ -705,7 +705,7 @@
"dataRetentionTimeError": "保留期限必須為 0 或正數",
"infiniteRetention": "設定為 0 以作無限期保留。",
"confirmDeleteTagMsg": "你確定你要刪除此標籤?相關的監測器不會被刪除。",
"twilioAuthToken": "認證 Token",
"twilioAuthToken": "認證 Token / API 金鑰",
"twilioAccountSID": "帳號 SID",
"ntfyUsernameAndPassword": "使用者名稱和密碼",
"ntfyAuthenticationMethod": "認證類型",
@@ -748,5 +748,41 @@
"Home": "首頁",
"chromeExecutable": "Chrome/Chromium 執行檔",
"chromeExecutableAutoDetect": "自動偵測",
"pagertreeCritical": "緊急"
"pagertreeCritical": "緊急",
"PushDeer Server": "PushDeer 伺服器",
"pushDeerServerDescription": "留空則使用官方伺服器",
"Body Encoding": "請求體編碼",
"filterActive": "啟用",
"filterActivePaused": "暫停",
"Select": "選擇",
"enableNSCD": "啟用 NSCD名稱服務緩存以緩存所有 DNS 請求",
"Server URL should not contain the nfty topic": "服務器地址不應包含 ntfy主題",
"Invert Keyword": "反轉模式",
"Request Timeout": "請求超時",
"timeoutAfter": "{0} 秒後超時",
"styleElapsedTime": "在監控項詳情的心跳欄下顯示起止時間",
"styleElapsedTimeShowNoLine": "顯示(不帶連接線)",
"styleElapsedTimeShowWithLine": "顯示(帶連接線)",
"webhookCustomBodyDesc": "為 webhook 設定一個自定義 HTTP 請求體。可在模板內使用{msg},、{heartbeat}和{monitor} 變量。",
"webhookBodyPresetOption": "預設 - {0}",
"webhookBodyCustomOption": "自定義內容",
"selectedMonitorCount": "已選:{0}",
"Check/Uncheck": "選中/取消選中",
"tailscalePingWarning": "如需使用 Tailscale Ping 客戶端,您需要以非 docker 方式安裝 Uptime Kuma並同時安裝 Tailscale 客戶端。",
"invertKeywordDescription": "出現關鍵詞將令檢測結果設為失敗,而非成功。",
"jsonQueryDescription": "對響應結果執行一次 JSON 查詢,其返回值將會被轉換為字符串,再與期望值進行比較。可訪問<a href='https://jsonata.org/'>jsonata.org</a> 閱讀JSON 查詢語言的文檔,或在<a href='https://try.jsonata.org/'>此處</a>測試查詢語句。",
"wayToGetKookGuildID": "在 Kook 設置中打開“開發者模式”,然後右鍵點擊頻道可獲取其 ID",
"Notify Channel": "通知該頻道",
"aboutNotifyChannel": "勾選“通知該頻道”,會令該頻道內所有成員都收到一條桌面端或移動端通知,無論其狀態是在線或離開。",
"pagertreeIntegrationUrl": "集成 URL 地址",
"pagertreeUrgency": "緊急程度",
"Expected Value": "預期值",
"Json Query": "JSON 查詢",
"setupDatabaseChooseDatabase": "您想使用什麼資料庫?",
"setupDatabaseEmbeddedMariaDB": "您不需要設置任何東西。 此 docker 映像檔已自動為您嵌入並配置了 MariaDB。 Uptime Kuma 將通過 unix 套接字連接到該資料庫。",
"setupDatabaseMariaDB": "連接到外部 MariaDB 資料庫。 需要設置資料庫連接資訊。",
"dbName": "資料庫名稱",
"Show Clickable Link": "顯示可點擊連結",
"pagertreeSilent": "靜音",
"twilioApiKey": "API 金鑰 (選用)"
}

View File

@@ -117,12 +117,23 @@
{{ $t("Settings") }}
</router-link>
</nav>
<button
v-if="numActiveToasts != 0"
type="button"
class="btn btn-normal clear-all-toast-btn"
@click="clearToasts"
>
<font-awesome-icon icon="times" />
</button>
</div>
</template>
<script>
import Login from "../components/Login.vue";
import compareVersions from "compare-versions";
import { useToast } from "vue-toastification";
const toast = useToast();
export default {
@@ -131,7 +142,11 @@ export default {
},
data() {
return {};
return {
toastContainer: null,
numActiveToasts: 0,
toastContainerObserver: null,
};
},
computed: {
@@ -159,11 +174,34 @@ export default {
},
mounted() {
this.toastContainer = document.querySelector(".bottom-right.toast-container");
// Watch the number of active toasts
this.toastContainerObserver = new MutationObserver((mutations) => {
for (const mutation of mutations) {
if (mutation.type === "childList") {
this.numActiveToasts = mutation.target.children.length;
}
}
});
if (this.toastContainer != null) {
this.toastContainerObserver.observe(this.toastContainer, { childList: true });
}
},
beforeUnmount() {
this.toastContainerObserver.disconnect();
},
methods: {
/**
* Clear all toast notifications.
* @returns {void}
*/
clearToasts() {
toast.clear();
}
},
};
@@ -323,4 +361,26 @@ main {
background-color: $dark-bg;
}
}
.clear-all-toast-btn {
position: fixed;
right: 1em;
bottom: 1em;
font-size: 1.2em;
padding: 9px 15px;
width: 48px;
box-shadow: 2px 2px 30px rgba(0, 0, 0, 0.2);
z-index: 100;
.dark & {
box-shadow: 2px 2px 30px rgba(0, 0, 0, 0.5);
}
}
@media (max-width: 770px) {
.clear-all-toast-btn {
bottom: 72px;
}
}
</style>

View File

@@ -20,6 +20,7 @@ import dayjs from "dayjs";
import timezone from "./modules/dayjs/plugin/timezone";
import utc from "dayjs/plugin/utc";
import relativeTime from "dayjs/plugin/relativeTime";
import { loadToastSettings } from "./util-frontend";
dayjs.extend(utc);
dayjs.extend(timezone);
dayjs.extend(relativeTime);
@@ -44,11 +45,7 @@ const app = createApp({
app.use(router);
app.use(i18n);
const options = {
position: "bottom-right",
};
app.use(Toast, options);
app.use(Toast, loadToastSettings());
app.component("Editable", contenteditable);
app.component("FontAwesomeIcon", FontAwesomeIcon);

View File

@@ -14,8 +14,9 @@ export default {
methods: {
/**
* Convert value to UTC
* @param {string | number | Date | dayjs.Dayjs} value
* @returns {dayjs.Dayjs}
* @param {string | number | Date | dayjs.Dayjs} value Time
* value to convert
* @returns {dayjs.Dayjs} Converted time
*/
toUTC(value) {
return dayjs.tz(value, this.timezone).utc().format();
@@ -23,8 +24,9 @@ export default {
/**
* Used for <input type="datetime" />
* @param value
* @returns {string}
* @param {string | number | Date | dayjs.Dayjs} value Value to
* convert
* @returns {string} Datetime string
*/
toDateTimeInputFormat(value) {
return this.datetimeFormat(value, "YYYY-MM-DDTHH:mm");
@@ -33,7 +35,7 @@ export default {
/**
* Return a given value in the format YYYY-MM-DD HH:mm:ss
* @param {any} value Value to format as date time
* @returns {string}
* @returns {string} Formatted string
*/
datetime(value) {
return this.datetimeFormat(value, "YYYY-MM-DD HH:mm:ss");
@@ -41,8 +43,9 @@ export default {
/**
* Get time for maintenance
* @param {string | number | Date | dayjs.Dayjs} value
* @returns {string}
* @param {string | number | Date | dayjs.Dayjs} value Time to
* format
* @returns {string} Formatted string
*/
datetimeMaintenance(value) {
const inputDate = new Date(value);
@@ -58,7 +61,7 @@ export default {
/**
* Return a given value in the format YYYY-MM-DD
* @param {any} value Value to format as date
* @returns {string}
* @returns {string} Formatted string
*/
date(value) {
return this.datetimeFormat(value, "YYYY-MM-DD");
@@ -69,7 +72,7 @@ export default {
* to true, HH:mm:ss
* @param {any} value Value to format
* @param {boolean} second Should seconds be included?
* @returns {string}
* @returns {string} Formatted string
*/
time(value, second = true) {
let secondString;
@@ -85,7 +88,7 @@ export default {
* Return a value in a custom format
* @param {any} value Value to format
* @param {any} format Format to return value in
* @returns {string}
* @returns {string} Formatted string
*/
datetimeFormat(value, format) {
if (value !== undefined && value !== "") {

View File

@@ -22,7 +22,11 @@ export default {
},
methods: {
/** Change the application language */
/**
* Change the application language
* @param {string} lang Language code to switch to
* @returns {Promise<void>}
*/
async changeLang(lang) {
let message = (await langModules["../lang/" + lang + ".json"]()).default;
this.$i18n.setLocaleMessage(lang, message);

View File

@@ -12,13 +12,19 @@ export default {
},
methods: {
/** Handle screen resize */
/**
* Handle screen resize
* @returns {void}
*/
onResize() {
this.windowWidth = window.innerWidth;
this.updateBody();
},
/** Add css-class "mobile" to body if needed */
/**
* Add css-class "mobile" to body if needed
* @returns {void}
*/
updateBody() {
if (this.isMobile) {
document.body.classList.add("mobile");

View File

@@ -3,8 +3,10 @@ import { useToast } from "vue-toastification";
import jwtDecode from "jwt-decode";
import Favico from "favico.js";
import dayjs from "dayjs";
import mitt from "mitt";
import { DOWN, MAINTENANCE, PENDING, UP } from "../util.ts";
import { getDevContainerServerHostname, isDevContainer } from "../util-frontend.js";
import { getDevContainerServerHostname, isDevContainer, getToastSuccessTimeout, getToastErrorTimeout } from "../util-frontend.js";
const toast = useToast();
let socket;
@@ -39,7 +41,6 @@ export default {
maintenanceList: {},
apiKeyList: {},
heartbeatList: { },
importantHeartbeatList: { },
avgPingList: { },
uptimeList: { },
tlsInfoList: {},
@@ -59,6 +60,7 @@ export default {
currentPassword: "",
},
faviconUpdateDebounce: null,
emitter: mitt(),
};
},
@@ -70,9 +72,9 @@ export default {
/**
* Initialize connection to socket server
* @param {boolean} [bypass = false] Should the check for if we
* @param {boolean} bypass Should the check for if we
* are on a status page be bypassed?
* @returns {(void|null)}
* @returns {void}
*/
initSocketIO(bypass = false) {
// No need to re-init
@@ -89,6 +91,11 @@ export default {
}
}
// Also don't need to connect to the socket.io for setup database page
if (location.pathname === "/setup-database") {
return;
}
this.socket.initedSocketIO = true;
let protocol = (location.protocol === "https:") ? "wss://" : "ws://";
@@ -185,22 +192,18 @@ export default {
if (this.monitorList[data.monitorID] !== undefined) {
if (data.status === 0) {
toast.error(`[${this.monitorList[data.monitorID].name}] [DOWN] ${data.msg}`, {
timeout: false,
timeout: getToastErrorTimeout(),
});
} else if (data.status === 1) {
toast.success(`[${this.monitorList[data.monitorID].name}] [Up] ${data.msg}`, {
timeout: 20000,
timeout: getToastSuccessTimeout(),
});
} else {
toast(`[${this.monitorList[data.monitorID].name}] ${data.msg}`);
}
}
if (! (data.monitorID in this.importantHeartbeatList)) {
this.importantHeartbeatList[data.monitorID] = [];
}
this.importantHeartbeatList[data.monitorID].unshift(data);
this.emitter.emit("newImportantHeartbeat", data);
}
});
@@ -224,14 +227,6 @@ export default {
this.tlsInfoList[monitorID] = JSON.parse(data);
});
socket.on("importantHeartbeatList", (monitorID, data, overwrite) => {
if (! (monitorID in this.importantHeartbeatList) || overwrite) {
this.importantHeartbeatList[monitorID] = data;
} else {
this.importantHeartbeatList[monitorID] = data.concat(this.importantHeartbeatList[monitorID]);
}
});
socket.on("connect_error", (err) => {
console.error(`Failed to connect to the backend. Socket.io connect_error: ${err.message}`);
this.connectionErrorMsg = `${this.$t("Cannot connect to the socket server.")} [${err}] ${this.$t("Reconnecting...")}`;
@@ -292,7 +287,7 @@ export default {
/**
* The storage currently in use
* @returns {Storage}
* @returns {Storage} Current storage
*/
storage() {
return (this.remember) ? localStorage : sessionStorage;
@@ -300,7 +295,7 @@ export default {
/**
* Get payload of JWT cookie
* @returns {(Object|undefined)}
* @returns {(object | undefined)} JWT payload
*/
getJWTPayload() {
const jwtToken = this.$root.storage().token;
@@ -313,7 +308,7 @@ export default {
/**
* Get current socket
* @returns {Socket}
* @returns {Socket} Current socket
*/
getSocket() {
return socket;
@@ -321,36 +316,48 @@ export default {
/**
* Show success or error toast dependant on response status code
* @param {Object} res Response object
* @param {object} res Response object
* @returns {void}
*/
toastRes(res) {
let msg = res.msg;
if (res.msgi18n) {
if (msg != null && typeof msg === "object") {
msg = this.$t(msg.key, msg.values);
} else {
msg = this.$t(msg);
}
}
if (res.ok) {
toast.success(res.msg);
toast.success(msg);
} else {
toast.error(res.msg);
toast.error(msg);
}
},
/**
* Show a success toast
* @param {string} msg Message to show
* @returns {void}
*/
toastSuccess(msg) {
toast.success(msg);
toast.success(this.$t(msg));
},
/**
* Show an error toast
* @param {string} msg Message to show
* @returns {void}
*/
toastError(msg) {
toast.error(msg);
toast.error(this.$t(msg));
},
/**
* Callback for login
* @callback loginCB
* @param {Object} res Response object
* @param {object} res Response object
*/
/**
@@ -359,6 +366,7 @@ export default {
* @param {string} password Password to log in with
* @param {string} token User token
* @param {loginCB} callback Callback to call with result
* @returns {void}
*/
login(username, password, token, callback) {
socket.emit("login", {
@@ -387,6 +395,7 @@ export default {
/**
* Log in using a token
* @param {string} token Token to log in with
* @returns {void}
*/
loginByToken(token) {
socket.emit("loginByToken", token, (res) => {
@@ -401,7 +410,10 @@ export default {
});
},
/** Log out of the web application */
/**
* Log out of the web application
* @returns {void}
*/
logout() {
socket.emit("logout", () => { });
this.storage().removeItem("token");
@@ -414,9 +426,13 @@ export default {
/**
* Callback for general socket requests
* @callback socketCB
* @param {Object} res Result of operation
* @param {object} res Result of operation
*/
/**
* Prepare 2FA configuration
* @param {socketCB} callback Callback for socket response
* @returns {void}
*/
/** Prepare 2FA configuration */
prepare2FA(callback) {
socket.emit("prepare2FA", callback);
},
@@ -424,7 +440,8 @@ export default {
/**
* Save the current 2FA configuration
* @param {any} secret Unused
* @param {socketCB} callback
* @param {socketCB} callback Callback for socket response
* @returns {void}
*/
save2FA(secret, callback) {
socket.emit("save2FA", callback);
@@ -432,7 +449,8 @@ export default {
/**
* Disable 2FA for this user
* @param {socketCB} callback
* @param {socketCB} callback Callback for socket response
* @returns {void}
*/
disable2FA(callback) {
socket.emit("disable2FA", callback);
@@ -441,7 +459,8 @@ export default {
/**
* Verify the provided 2FA token
* @param {string} token Token to verify
* @param {socketCB} callback
* @param {socketCB} callback Callback for socket response
* @returns {void}
*/
verifyToken(token, callback) {
socket.emit("verifyToken", token, callback);
@@ -449,7 +468,8 @@ export default {
/**
* Get current 2FA status
* @param {socketCB} callback
* @param {socketCB} callback Callback for socket response
* @returns {void}
*/
twoFAStatus(callback) {
socket.emit("twoFAStatus", callback);
@@ -457,7 +477,8 @@ export default {
/**
* Get list of monitors
* @param {socketCB} callback
* @param {socketCB} callback Callback for socket response
* @returns {void}
*/
getMonitorList(callback) {
if (! callback) {
@@ -468,7 +489,8 @@ export default {
/**
* Get list of maintenances
* @param {socketCB} callback
* @param {socketCB} callback Callback for socket response
* @returns {void}
*/
getMaintenanceList(callback) {
if (! callback) {
@@ -479,7 +501,8 @@ export default {
/**
* Send list of API keys
* @param {socketCB} callback
* @param {socketCB} callback Callback for socket response
* @returns {void}
*/
getAPIKeyList(callback) {
if (!callback) {
@@ -490,17 +513,19 @@ export default {
/**
* Add a monitor
* @param {Object} monitor Object representing monitor to add
* @param {socketCB} callback
* @param {object} monitor Object representing monitor to add
* @param {socketCB} callback Callback for socket response
* @returns {void}
*/
add(monitor, callback) {
socket.emit("add", monitor, callback);
},
/**
* Adds a maintenace
* @param {Object} maintenance
* @param {socketCB} callback
* Adds a maintenance
* @param {object} maintenance Maintenance to add
* @param {socketCB} callback Callback for socket response
* @returns {void}
*/
addMaintenance(maintenance, callback) {
socket.emit("addMaintenance", maintenance, callback);
@@ -508,9 +533,10 @@ export default {
/**
* Add monitors to maintenance
* @param {number} maintenanceID
* @param {number[]} monitors
* @param {socketCB} callback
* @param {number} maintenanceID Maintenance to modify
* @param {number[]} monitors IDs of monitors to add
* @param {socketCB} callback Callback for socket response
* @returns {void}
*/
addMonitorMaintenance(maintenanceID, monitors, callback) {
socket.emit("addMonitorMaintenance", maintenanceID, monitors, callback);
@@ -518,9 +544,10 @@ export default {
/**
* Add status page to maintenance
* @param {number} maintenanceID
* @param {number} statusPages
* @param {socketCB} callback
* @param {number} maintenanceID Maintenance to modify
* @param {number} statusPages ID of status page to add
* @param {socketCB} callback Callback for socket response
* @returns {void}
*/
addMaintenanceStatusPage(maintenanceID, statusPages, callback) {
socket.emit("addMaintenanceStatusPage", maintenanceID, statusPages, callback);
@@ -528,8 +555,9 @@ export default {
/**
* Get monitors affected by maintenance
* @param {number} maintenanceID
* @param {socketCB} callback
* @param {number} maintenanceID Maintenance to read
* @param {socketCB} callback Callback for socket response
* @returns {void}
*/
getMonitorMaintenance(maintenanceID, callback) {
socket.emit("getMonitorMaintenance", maintenanceID, callback);
@@ -537,8 +565,9 @@ export default {
/**
* Get status pages where maintenance is shown
* @param {number} maintenanceID
* @param {socketCB} callback
* @param {number} maintenanceID Maintenance to read
* @param {socketCB} callback Callback for socket response
* @returns {void}
*/
getMaintenanceStatusPage(maintenanceID, callback) {
socket.emit("getMaintenanceStatusPage", maintenanceID, callback);
@@ -547,7 +576,8 @@ export default {
/**
* Delete monitor by ID
* @param {number} monitorID ID of monitor to delete
* @param {socketCB} callback
* @param {socketCB} callback Callback for socket response
* @returns {void}
*/
deleteMonitor(monitorID, callback) {
socket.emit("deleteMonitor", monitorID, callback);
@@ -555,8 +585,9 @@ export default {
/**
* Delete specified maintenance
* @param {number} maintenanceID
* @param {socketCB} callback
* @param {number} maintenanceID Maintenance to delete
* @param {socketCB} callback Callback for socket response
* @returns {void}
*/
deleteMaintenance(maintenanceID, callback) {
socket.emit("deleteMaintenance", maintenanceID, callback);
@@ -564,8 +595,9 @@ export default {
/**
* Add an API key
* @param {Object} key API key to add
* @param {socketCB} callback
* @param {object} key API key to add
* @param {socketCB} callback Callback for socket response
* @returns {void}
*/
addAPIKey(key, callback) {
socket.emit("addAPIKey", key, callback);
@@ -574,17 +606,20 @@ export default {
/**
* Delete specified API key
* @param {int} keyID ID of key to delete
* @param {socketCB} callback
* @param {socketCB} callback Callback for socket response
* @returns {void}
*/
deleteAPIKey(keyID, callback) {
socket.emit("deleteAPIKey", keyID, callback);
},
/** Clear the hearbeat list */
/**
* Clear the hearbeat list
* @returns {void}
*/
clearData() {
console.log("reset heartbeat list");
this.heartbeatList = {};
this.importantHeartbeatList = {};
},
/**
@@ -592,7 +627,8 @@ export default {
* @param {string} uploadedJSON JSON to upload
* @param {string} importHandle Type of import. If set to
* most data in database will be replaced
* @param {socketCB} callback
* @param {socketCB} callback Callback for socket response
* @returns {void}
*/
uploadBackup(uploadedJSON, importHandle, callback) {
socket.emit("uploadBackup", uploadedJSON, importHandle, callback);
@@ -601,7 +637,8 @@ export default {
/**
* Clear events for a specified monitor
* @param {number} monitorID ID of monitor to clear
* @param {socketCB} callback
* @param {socketCB} callback Callback for socket response
* @returns {void}
*/
clearEvents(monitorID, callback) {
socket.emit("clearEvents", monitorID, callback);
@@ -610,7 +647,8 @@ export default {
/**
* Clear the heartbeats of a specified monitor
* @param {number} monitorID Id of monitor to clear
* @param {socketCB} callback
* @param {socketCB} callback Callback for socket response
* @returns {void}
*/
clearHeartbeats(monitorID, callback) {
socket.emit("clearHeartbeats", monitorID, callback);
@@ -618,7 +656,8 @@ export default {
/**
* Clear all statistics
* @param {socketCB} callback
* @param {socketCB} callback Callback for socket response
* @returns {void}
*/
clearStatistics(callback) {
socket.emit("clearStatistics", callback);
@@ -628,11 +667,12 @@ export default {
* Get monitor beats for a specific monitor in a time range
* @param {number} monitorID ID of monitor to fetch
* @param {number} period Time in hours from now
* @param {socketCB} callback
* @param {socketCB} callback Callback for socket response
* @returns {void}
*/
getMonitorBeats(monitorID, period, callback) {
socket.emit("getMonitorBeats", monitorID, period, callback);
}
},
},
computed: {
@@ -739,7 +779,7 @@ export default {
* Frontend Version
* It should be compiled to a static value while building the frontend.
* Please see ./config/vite.config.js, it is defined via vite.js
* @returns {string}
* @returns {string} Current version
*/
frontendVersion() {
// eslint-disable-next-line no-undef
@@ -748,7 +788,7 @@ export default {
/**
* Are both frontend and backend in the same version?
* @returns {boolean}
* @returns {boolean} The frontend and backend match?
*/
isFrontendBackendVersionMatched() {
if (!this.info.version) {

View File

@@ -95,7 +95,10 @@ export default {
},
methods: {
/** Update the theme color meta tag */
/**
* Update the theme color meta tag
* @returns {void}
*/
updateThemeColorMeta() {
if (this.theme === "dark") {
document.querySelector("#theme-color").setAttribute("content", "#161B22");

View File

@@ -51,7 +51,10 @@ export default {
};
},
methods: {
/** Submit form data to add new status page */
/**
* Submit form data to add new status page
* @returns {Promise<void>}
*/
async submit() {
this.processing = true;
@@ -63,7 +66,7 @@ export default {
} else {
if (res.msg.includes("UNIQUE constraint")) {
this.$root.toastError(this.$t("The slug is already taken. Please choose another slug."));
this.$root.toastError("The slug is already taken. Please choose another slug.");
} else {
this.$root.toastRes(res);
}

View File

@@ -42,13 +42,13 @@
</thead>
<tbody>
<tr v-for="(beat, index) in displayedRecords" :key="index" :class="{ 'shadow-box': $root.windowWidth <= 550}">
<td><router-link :to="`/dashboard/${beat.monitorID}`">{{ beat.name }}</router-link></td>
<td><router-link :to="`/dashboard/${beat.monitorID}`">{{ $root.monitorList[beat.monitorID]?.name }}</router-link></td>
<td><Status :status="beat.status" /></td>
<td :class="{ 'border-0':! beat.msg}"><Datetime :value="beat.time" /></td>
<td class="border-0">{{ beat.msg }}</td>
</tr>
<tr v-if="importantHeartBeatList.length === 0">
<tr v-if="importantHeartBeatListLength === 0">
<td colspan="4">
{{ $t("No important events") }}
</td>
@@ -59,7 +59,7 @@
<div class="d-flex justify-content-center kuma_pagination">
<pagination
v-model="page"
:records="importantHeartBeatList.length"
:records="importantHeartBeatListLength"
:per-page="perPage"
:options="paginationConfig"
/>
@@ -92,72 +92,89 @@ export default {
page: 1,
perPage: 25,
initialPerPage: 25,
heartBeatList: [],
paginationConfig: {
hideCount: true,
chunksNavigation: "scroll",
},
importantHeartBeatListLength: 0,
displayedRecords: [],
};
},
computed: {
importantHeartBeatList() {
let result = [];
for (let monitorID in this.$root.importantHeartbeatList) {
let list = this.$root.importantHeartbeatList[monitorID];
result = result.concat(list);
}
for (let beat of result) {
let monitor = this.$root.monitorList[beat.monitorID];
if (monitor) {
beat.name = monitor.name;
}
}
result.sort((a, b) => {
if (a.time > b.time) {
return -1;
}
if (a.time < b.time) {
return 1;
}
return 0;
});
// eslint-disable-next-line vue/no-side-effects-in-computed-properties
this.heartBeatList = result;
return result;
},
displayedRecords() {
const startIndex = this.perPage * (this.page - 1);
const endIndex = startIndex + this.perPage;
return this.heartBeatList.slice(startIndex, endIndex);
},
},
watch: {
importantHeartBeatList() {
perPage() {
this.$nextTick(() => {
this.updatePerPage();
this.getImportantHeartbeatListPaged();
});
},
page() {
this.getImportantHeartbeatListPaged();
},
},
mounted() {
this.getImportantHeartbeatListLength();
this.$root.emitter.on("newImportantHeartbeat", this.onNewImportantHeartbeat);
this.initialPerPage = this.perPage;
window.addEventListener("resize", this.updatePerPage);
this.updatePerPage();
},
beforeUnmount() {
this.$root.emitter.off("newImportantHeartbeat", this.onNewImportantHeartbeat);
window.removeEventListener("resize", this.updatePerPage);
},
methods: {
/**
* Updates the displayed records when a new important heartbeat arrives.
* @param {object} heartbeat - The heartbeat object received.
* @returns {void}
*/
onNewImportantHeartbeat(heartbeat) {
if (this.page === 1) {
this.displayedRecords.unshift(heartbeat);
if (this.displayedRecords.length > this.perPage) {
this.displayedRecords.pop();
}
this.importantHeartBeatListLength += 1;
}
},
/**
* Retrieves the length of the important heartbeat list for all monitors.
* @returns {void}
*/
getImportantHeartbeatListLength() {
this.$root.getSocket().emit("monitorImportantHeartbeatListCount", null, (res) => {
if (res.ok) {
this.importantHeartBeatListLength = res.count;
this.getImportantHeartbeatListPaged();
}
});
},
/**
* Retrieves the important heartbeat list for the current page.
* @returns {void}
*/
getImportantHeartbeatListPaged() {
const offset = (this.page - 1) * this.perPage;
this.$root.getSocket().emit("monitorImportantHeartbeatListPaged", null, offset, this.perPage, (res) => {
if (res.ok) {
this.displayedRecords = res.data;
}
});
},
/**
* Updates the number of items shown per page based on the available height.
* @returns {void}
*/
updatePerPage() {
const tableContainer = this.$refs.tableContainer;
const tableContainerHeight = tableContainer.offsetHeight;

View File

@@ -76,6 +76,34 @@
</div>
</div>
<!-- Push Examples -->
<div v-if="monitor.type === 'push'" class="shadow-box big-padding">
<a href="#" @click="pushMonitor.showPushExamples = !pushMonitor.showPushExamples">{{ $t("pushViewCode") }}</a>
<transition name="slide-fade" appear>
<div v-if="pushMonitor.showPushExamples" class="mt-3">
<select id="push-current-example" v-model="pushMonitor.currentExample" class="form-select">
<optgroup :label="$t('programmingLanguages')">
<option value="csharp">C#</option>
<option value="go">Go</option>
<option value="java">Java</option>
<option value="javascript-fetch">JavaScript (fetch)</option>
<option value="php">PHP</option>
<option value="python">Python</option>
<option value="typescript-fetch">TypeScript (fetch)</option>
</optgroup>
<optgroup :label="$t('pushOthers')">
<option value="bash-curl">Bash (curl)</option>
<option value="powershell">PowerShell</option>
<option value="docker">Docker</option>
</optgroup>
</select>
<prism-editor v-model="pushMonitor.code" class="css-editor mt-3" :highlight="pushExampleHighlighter" line-numbers readonly></prism-editor>
</div>
</transition>
</div>
<!-- Stats -->
<div class="shadow-box big-padding text-center stats">
<div class="row">
@@ -95,6 +123,8 @@
<CountUp :value="avgPing" />
</span>
</div>
<!-- Uptime (24-hour) -->
<div class="col-12 col-sm col row d-flex align-items-center d-sm-block">
<h4 class="col-4 col-sm-12">{{ $t("Uptime") }}</h4>
<p class="col-4 col-sm-12 mb-0 mb-sm-2">(24{{ $t("-hour") }})</p>
@@ -102,6 +132,8 @@
<Uptime :monitor="monitor" type="24" />
</span>
</div>
<!-- Uptime (30-day) -->
<div class="col-12 col-sm col row d-flex align-items-center d-sm-block">
<h4 class="col-4 col-sm-12">{{ $t("Uptime") }}</h4>
<p class="col-4 col-sm-12 mb-0 mb-sm-2">(30{{ $t("-day") }})</p>
@@ -110,6 +142,15 @@
</span>
</div>
<!-- Uptime (1-year) -->
<div class="col-12 col-sm col row d-flex align-items-center d-sm-block">
<h4 class="col-4 col-sm-12">{{ $t("Uptime") }}</h4>
<p class="col-4 col-sm-12 mb-0 mb-sm-2">(1{{ $t("-year") }})</p>
<span class="col-4 col-sm-12 num">
<Uptime :monitor="monitor" type="1y" />
</span>
</div>
<div v-if="tlsInfo" class="col-12 col-sm col row d-flex align-items-center d-sm-block">
<h4 class="col-4 col-sm-12">{{ $t("Cert Exp.") }}</h4>
<p class="col-4 col-sm-12 mb-0 mb-sm-2">(<Datetime :value="tlsInfo.certInfo.validTo" date-only />)</p>
@@ -182,7 +223,7 @@
<td class="border-0">{{ beat.msg }}</td>
</tr>
<tr v-if="importantHeartBeatList.length === 0">
<tr v-if="importantHeartBeatListLength === 0">
<td colspan="3">
{{ $t("No important events") }}
</td>
@@ -193,7 +234,7 @@
<div class="d-flex justify-content-center kuma_pagination">
<pagination
v-model="page"
:records="importantHeartBeatList.length"
:records="importantHeartBeatListLength"
:per-page="perPage"
:options="paginationConfig"
/>
@@ -236,6 +277,12 @@ import CertificateInfo from "../components/CertificateInfo.vue";
import { getMonitorRelativeURL } from "../util.ts";
import { URL } from "whatwg-url";
import { getResBaseURL } from "../util-frontend";
import { highlight, languages } from "prismjs/components/prism-core";
import "prismjs/components/prism-clike";
import "prismjs/components/prism-javascript";
import "prismjs/components/prism-css";
import { PrismEditor } from "vue-prism-editor";
import "vue-prism-editor/dist/prismeditor.min.css";
export default {
components: {
@@ -249,6 +296,7 @@ export default {
PingChart,
Tag,
CertificateInfo,
PrismEditor,
},
data() {
return {
@@ -262,6 +310,13 @@ export default {
chunksNavigation: "scroll",
},
cacheTime: Date.now(),
importantHeartBeatListLength: 0,
displayedRecords: [],
pushMonitor: {
showPushExamples: false,
currentExample: "javascript-fetch",
code: "",
},
};
},
computed: {
@@ -300,16 +355,6 @@ export default {
return this.$t("notAvailableShort");
},
importantHeartBeatList() {
if (this.$root.importantHeartbeatList[this.monitor.id]) {
// eslint-disable-next-line vue/no-side-effects-in-computed-properties
this.heartBeatList = this.$root.importantHeartbeatList[this.monitor.id];
return this.$root.importantHeartbeatList[this.monitor.id];
}
return [];
},
status() {
if (this.$root.statusList[this.monitor.id]) {
return this.$root.statusList[this.monitor.id];
@@ -333,12 +378,6 @@ export default {
return this.tlsInfo != null && this.toggleCertInfoBox;
},
displayedRecords() {
const startIndex = this.perPage * (this.page - 1);
const endIndex = startIndex + this.perPage;
return this.heartBeatList.slice(startIndex, endIndex);
},
group() {
if (!this.monitor.pathName.includes("/")) {
return "";
@@ -354,73 +393,136 @@ export default {
return getResBaseURL() + this.monitor.screenshot + "?time=" + this.cacheTime;
}
},
mounted() {
},
methods: {
getResBaseURL,
/** Request a test notification be sent for this monitor */
testNotification() {
this.$root.getSocket().emit("testNotification", this.monitor.id);
toast.success("Test notification is requested.");
watch: {
page(to) {
this.getImportantHeartbeatListPaged();
},
/** Show dialog to confirm pause */
monitor(to) {
this.getImportantHeartbeatListLength();
},
"monitor.type"() {
if (this.monitor && this.monitor.type === "push") {
this.loadPushExample();
}
},
"pushMonitor.currentExample"() {
this.loadPushExample();
},
},
mounted() {
this.getImportantHeartbeatListLength();
this.$root.emitter.on("newImportantHeartbeat", this.onNewImportantHeartbeat);
if (this.monitor && this.monitor.type === "push") {
if (this.lastHeartBeat.status === -1) {
this.pushMonitor.showPushExamples = true;
}
this.loadPushExample();
}
},
beforeUnmount() {
this.$root.emitter.off("newImportantHeartbeat", this.onNewImportantHeartbeat);
},
methods: {
getResBaseURL,
/**
* Request a test notification be sent for this monitor
* @returns {void}
*/
testNotification() {
this.$root.getSocket().emit("testNotification", this.monitor.id);
this.$root.toastSuccess("Test notification is requested.");
},
/**
* Show dialog to confirm pause
* @returns {void}
*/
pauseDialog() {
this.$refs.confirmPause.show();
},
/** Resume this monitor */
/**
* Resume this monitor
* @returns {void}
*/
resumeMonitor() {
this.$root.getSocket().emit("resumeMonitor", this.monitor.id, (res) => {
this.$root.toastRes(res);
});
},
/** Request that this monitor is paused */
/**
* Request that this monitor is paused
* @returns {void}
*/
pauseMonitor() {
this.$root.getSocket().emit("pauseMonitor", this.monitor.id, (res) => {
this.$root.toastRes(res);
});
},
/** Show dialog to confirm deletion */
/**
* Show dialog to confirm deletion
* @returns {void}
*/
deleteDialog() {
this.$refs.confirmDelete.show();
},
/** Show dialog to confirm clearing events */
/**
* Show dialog to confirm clearing events
* @returns {void}
*/
clearEventsDialog() {
this.$refs.confirmClearEvents.show();
},
/** Show dialog to confirm clearing heartbeats */
/**
* Show dialog to confirm clearing heartbeats
* @returns {void}
*/
clearHeartbeatsDialog() {
this.$refs.confirmClearHeartbeats.show();
},
/** Request that this monitor is deleted */
/**
* Request that this monitor is deleted
* @returns {void}
*/
deleteMonitor() {
this.$root.deleteMonitor(this.monitor.id, (res) => {
this.$root.toastRes(res);
if (res.ok) {
toast.success(res.msg);
this.$router.push("/dashboard");
}
});
},
/**
* Request that this monitors events are cleared
* @returns {void}
*/
clearEvents() {
this.$root.clearEvents(this.monitor.id, (res) => {
if (res.ok) {
this.getImportantHeartbeatListLength();
} else {
toast.error(res.msg);
}
});
},
/** Request that this monitors events are cleared */
clearEvents() {
this.$root.clearEvents(this.monitor.id, (res) => {
if (! res.ok) {
toast.error(res.msg);
}
});
},
/** Request that this monitors heartbeats are cleared */
/**
* Request that this monitors heartbeats are cleared
* @returns {void}
*/
clearHeartbeats() {
this.$root.clearHeartbeats(this.monitor.id, (res) => {
if (! res.ok) {
@@ -431,8 +533,8 @@ export default {
/**
* Return the correct title for the ping stat
* @param {boolean} [average=false] Is the statistic an average?
* @returns {string} Title formated dependant on monitor type
* @param {boolean} average Is the statistic an average?
* @returns {string} Title formatted dependant on monitor type
*/
pingTitle(average = false) {
let translationPrefix = "";
@@ -456,7 +558,11 @@ export default {
return getMonitorRelativeURL(id);
},
/** Filter and hide password in URL for display */
/**
* Filter and hide password in URL for display
* @param {string} urlString URL to censor
* @returns {string} Censored URL
*/
filterPassword(urlString) {
try {
let parsedUrl = new URL(urlString);
@@ -468,6 +574,72 @@ export default {
// Handle SQL Server
return urlString.replaceAll(/Password=(.+);/ig, "Password=******;");
}
},
/**
* Retrieves the length of the important heartbeat list for this monitor.
* @returns {void}
*/
getImportantHeartbeatListLength() {
if (this.monitor) {
this.$root.getSocket().emit("monitorImportantHeartbeatListCount", this.monitor.id, (res) => {
if (res.ok) {
this.importantHeartBeatListLength = res.count;
this.getImportantHeartbeatListPaged();
}
});
}
},
/**
* Retrieves the important heartbeat list for the current page.
* @returns {void}
*/
getImportantHeartbeatListPaged() {
if (this.monitor) {
const offset = (this.page - 1) * this.perPage;
this.$root.getSocket().emit("monitorImportantHeartbeatListPaged", this.monitor.id, offset, this.perPage, (res) => {
if (res.ok) {
this.displayedRecords = res.data;
}
});
}
},
/**
* Updates the displayed records when a new important heartbeat arrives.
* @param {object} heartbeat - The heartbeat object received.
* @returns {void}
*/
onNewImportantHeartbeat(heartbeat) {
if (heartbeat.monitorID === this.monitor?.id) {
if (this.page === 1) {
this.displayedRecords.unshift(heartbeat);
if (this.displayedRecords.length > this.perPage) {
this.displayedRecords.pop();
}
this.importantHeartBeatListLength += 1;
}
}
},
/**
* Highlight the example code
* @param {string} code Code
* @returns {string} Highlighted code
*/
pushExampleHighlighter(code) {
return highlight(code, languages.js);
},
loadPushExample() {
this.pushMonitor.code = "";
this.$root.getSocket().emit("getPushExample", this.pushMonitor.currentExample, (res) => {
let code = res.code
.replace("60", this.monitor.interval)
.replace("https://example.com/api/push/key?status=up&msg=OK&ping=", this.pushURL);
this.pushMonitor.code = code;
});
}
},
};

View File

@@ -246,14 +246,11 @@
</template>
<script>
import { useToast } from "vue-toastification";
import VueMultiselect from "vue-multiselect";
import Datepicker from "@vuepic/vue-datepicker";
import { timezoneList } from "../util-frontend";
import cronstrue from "cronstrue/i18n";
const toast = useToast();
export default {
components: {
VueMultiselect,
@@ -417,7 +414,10 @@ export default {
});
},
methods: {
/** Initialise page */
/**
* Initialise page
* @returns {void}
*/
init() {
this.affectedMonitors = [];
this.selectedStatusPages = [];
@@ -454,7 +454,7 @@ export default {
this.affectedMonitors.push(this.affectedMonitorsOptions.find(item => item.id === monitor.id));
});
} else {
toast.error(res.msg);
this.$root.toastError(res.msg);
}
});
@@ -469,22 +469,25 @@ export default {
this.showOnAllPages = Object.values(res.statusPages).length === this.selectedStatusPagesOptions.length;
} else {
toast.error(res.msg);
this.$root.toastError(res.msg);
}
});
} else {
toast.error(res.msg);
this.$root.toastError(res.msg);
}
});
}
},
/** Create new maintenance */
/**
* Create new maintenance
* @returns {Promise<void>}
*/
async submit() {
this.processing = true;
if (this.affectedMonitors.length === 0) {
toast.error(this.$t("atLeastOneMonitor"));
this.$root.toastError(this.$t("atLeastOneMonitor"));
return this.processing = false;
}
@@ -493,14 +496,14 @@ export default {
if (res.ok) {
await this.addMonitorMaintenance(res.maintenanceID, async () => {
await this.addMaintenanceStatusPage(res.maintenanceID, () => {
toast.success(res.msg);
this.$root.toastRes(res);
this.processing = false;
this.$root.getMaintenanceList();
this.$router.push("/maintenance");
});
});
} else {
toast.error(res.msg);
this.$root.toastRes(res);
this.processing = false;
}
@@ -518,7 +521,7 @@ export default {
});
} else {
this.processing = false;
toast.error(res.msg);
this.$root.toastError(res.msg);
}
});
}
@@ -526,13 +529,14 @@ export default {
/**
* Add monitor to maintenance
* @param {number} maintenanceID
* @param {socketCB} callback
* @param {number} maintenanceID ID of maintenance to modify
* @param {socketCB} callback Callback for socket response
* @returns {Promise<void>}
*/
async addMonitorMaintenance(maintenanceID, callback) {
await this.$root.addMonitorMaintenance(maintenanceID, this.affectedMonitors, async (res) => {
if (!res.ok) {
toast.error(res.msg);
this.$root.toastError(res.msg);
} else {
this.$root.getMonitorList();
}
@@ -543,13 +547,14 @@ export default {
/**
* Add status page to maintenance
* @param {number} maintenanceID
* @param {socketCB} callback
* @param {number} maintenanceID ID of maintenance to modify
* @param {socketCB} callback Callback for socket response
* @returns {void}
*/
async addMaintenanceStatusPage(maintenanceID, callback) {
await this.$root.addMaintenanceStatusPage(maintenanceID, (this.showOnAllPages) ? this.selectedStatusPagesOptions : this.selectedStatusPages, async (res) => {
if (!res.ok) {
toast.error(res.msg);
this.$root.toastError(res.msg);
} else {
this.$root.getMaintenanceList();
}

View File

@@ -119,6 +119,9 @@
{{ $t("needPushEvery", [monitor.interval]) }}<br />
{{ $t("pushOptionalParams", ["status, msg, ping"]) }}
</div>
<button class="btn btn-primary" type="button" @click="resetToken">
{{ $t("Reset Token") }}
</button>
</div>
<!-- Keyword -->
@@ -658,6 +661,7 @@
<label for="httpBodyEncoding" class="form-label">{{ $t("Body Encoding") }}</label>
<select id="httpBodyEncoding" v-model="monitor.httpBodyEncoding" class="form-select">
<option value="json">JSON</option>
<option value="form">x-www-form-urlencoded</option>
<option value="xml">XML</option>
</select>
</div>
@@ -849,7 +853,9 @@ import { hostNameRegexPattern } from "../util-frontend";
import { sleep } from "../util";
import HiddenInput from "../components/HiddenInput.vue";
const toast = useToast();
const toast = useToast;
const pushTokenLength = 32;
const monitorDefaults = {
type: "http",
@@ -1010,6 +1016,9 @@ message HealthCheckResponse {
</soap:Body>
</soap:Envelope>` ]);
}
if (this.monitor && this.monitor.httpBodyEncoding === "form") {
return this.$t("Example:", [ "key1=value1&key2=value2" ]);
}
return this.$t("Example:", [ `
{
"key": "value"
@@ -1077,8 +1086,7 @@ message HealthCheckResponse {
/**
* Generates the parent monitor options list based on the sorted group monitor list and draft group name.
*
* @return {Array} The parent monitor options list.
* @returns {Array} The parent monitor options list.
*/
parentMonitorOptionsList() {
let list = [];
@@ -1164,7 +1172,9 @@ message HealthCheckResponse {
"monitor.type"() {
if (this.monitor.type === "push") {
if (! this.monitor.pushToken) {
this.monitor.pushToken = genSecret(10);
// ideally this would require checking if the generated token is already used
// it's very unlikely to get a collision though (62^32 ~ 2.27265788 * 10^57 unique tokens)
this.monitor.pushToken = genSecret(pushTokenLength);
}
}
@@ -1185,7 +1195,7 @@ message HealthCheckResponse {
if (res.ok) {
this.gameList = res.gameList;
} else {
toast.error(res.msg);
this.$root.toastError(res.msg);
}
});
}
@@ -1257,7 +1267,10 @@ message HealthCheckResponse {
this.kafkaSaslMechanismOptions = kafkaSaslMechanismOptions;
},
methods: {
/** Initialize the edit monitor form */
/**
* Initialize the edit monitor form
* @returns {void}
*/
init() {
if (this.isAdd) {
@@ -1327,7 +1340,7 @@ message HealthCheckResponse {
this.monitor.timeout = ~~(this.monitor.interval * 8) / 10;
}
} else {
toast.error(res.msg);
this.$root.toastError(res.msg);
}
});
}
@@ -1370,6 +1383,10 @@ message HealthCheckResponse {
return true;
},
resetToken() {
this.monitor.pushToken = genSecret(pushTokenLength);
},
/**
* Submit the form data for processing
* @returns {void}
@@ -1423,7 +1440,7 @@ message HealthCheckResponse {
createdNewParent = true;
this.monitor.parent = res.monitorID;
} else {
toast.error(res.msg);
this.$root.toastError(res.msg);
this.processing = false;
return;
}
@@ -1439,17 +1456,14 @@ message HealthCheckResponse {
if (createdNewParent) {
this.startParentGroupMonitor();
}
toast.success(res.msg);
this.processing = false;
this.$root.getMonitorList();
this.$router.push("/dashboard/" + res.monitorID);
} else {
toast.error(res.msg);
this.processing = false;
}
this.$root.toastRes(res);
});
} else {
await this.$refs.tagsManager.submit(this.monitor.id);
@@ -1476,6 +1490,7 @@ message HealthCheckResponse {
* Added a Notification Event
* Enable it if the notification is added in EditMonitor.vue
* @param {number} id ID of notification to add
* @returns {void}
*/
addedNotification(id) {
this.monitor.notificationIDList[id] = true;
@@ -1485,21 +1500,26 @@ message HealthCheckResponse {
* Added a Proxy Event
* Enable it if the proxy is added in EditMonitor.vue
* @param {number} id ID of proxy to add
* @returns {void}
*/
addedProxy(id) {
this.monitor.proxyId = id;
},
// Added a Docker Host Event
// Enable it if the Docker Host is added in EditMonitor.vue
/**
* Added a Docker Host Event
* Enable it if the Docker Host is added in EditMonitor.vue
* @param {number} id ID of docker host
* @returns {void}
*/
addedDockerHost(id) {
this.monitor.docker_host = id;
},
/**
* Adds a draft group.
*
* @param {string} draftGroupName - The name of the draft group.
* @param {string} draftGroupName The name of the draft group.
* @returns {void}
*/
addedDraftGroup(draftGroupName) {
this.draftGroupName = draftGroupName;

View File

@@ -19,25 +19,33 @@ export default {
},
async mounted() {
// There are only 2 cases that could come in here.
// There are only 3 cases that could come in here.
// 1. Matched status Page domain name
// 2. Vue Frontend Dev
let res = (await axios.get("/api/entry-page")).data;
// 3. Vue Frontend Dev (not setup database yet)
let res;
try {
res = (await axios.get("/api/entry-page")).data;
if (res.type === "statusPageMatchedDomain") {
this.statusPageSlug = res.statusPageSlug;
this.$root.forceStatusPageTheme = true;
if (res.type === "statusPageMatchedDomain") {
this.statusPageSlug = res.statusPageSlug;
this.$root.forceStatusPageTheme = true;
} else if (res.type === "entryPage") { // Dev only. For production, the logic is in the server side
const entryPage = res.entryPage;
} else if (res.type === "entryPage") { // Dev only. For production, the logic is in the server side
const entryPage = res.entryPage;
if (entryPage === "statusPage") {
this.$router.push("/status");
if (entryPage === "statusPage") {
this.$router.push("/status");
} else {
this.$router.push("/dashboard");
}
} else if (res.type === "setup-database") {
this.$router.push("/setup-database");
} else {
this.$router.push("/dashboard");
}
} else {
this.$router.push("/dashboard");
} catch (e) {
alert("Cannot connect to the backend server. Did you start the backend server? (npm run start-server-dev)");
}
},

View File

@@ -65,7 +65,10 @@ export default {
this.init();
},
methods: {
/** Initialise page */
/**
* Initialise page
* @returns {void}
*/
init() {
this.$root.getSocket().emit("getMonitorMaintenance", this.$route.params.id, (res) => {
if (res.ok) {
@@ -84,20 +87,22 @@ export default {
});
},
/** Confirm deletion */
/**
* Confirm deletion
* @returns {void}
*/
deleteDialog() {
this.$refs.confirmDelete.show();
},
/** Delete maintenance after showing confirmation */
/**
* Delete maintenance after showing confirmation
* @returns {void}
*/
deleteMaintenance() {
this.$root.deleteMaintenance(this.maintenance.id, (res) => {
if (res.ok) {
toast.success(res.msg);
this.$router.push("/maintenance");
} else {
toast.error(res.msg);
}
this.$root.toastRes(res);
this.$router.push("/maintenance");
});
},
},

View File

@@ -81,8 +81,6 @@ import { getResBaseURL } from "../util-frontend";
import { getMaintenanceRelativeURL } from "../util.ts";
import Confirm from "../components/Confirm.vue";
import MaintenanceTime from "../components/MaintenanceTime.vue";
import { useToast } from "vue-toastification";
const toast = useToast();
export default {
components: {
@@ -135,7 +133,7 @@ export default {
/**
* Get maintenance URL
* @param {number} id
* @param {number} id ID of maintenance to read
* @returns {string} Relative URL
*/
maintenanceURL(id) {
@@ -144,27 +142,33 @@ export default {
/**
* Show delete confirmation
* @param {number} maintenanceID
* @param {number} maintenanceID ID of maintenance to show delete
* confirmation for.
* @returns {void}
*/
deleteDialog(maintenanceID) {
this.selectedMaintenanceID = maintenanceID;
this.$refs.confirmDelete.show();
},
/** Delete maintenance after showing confirmation dialog */
/**
* Delete maintenance after showing confirmation dialog
* @returns {void}
*/
deleteMaintenance() {
this.$root.deleteMaintenance(this.selectedMaintenanceID, (res) => {
this.$root.toastRes(res);
if (res.ok) {
toast.success(res.msg);
this.$router.push("/maintenance");
} else {
toast.error(res.msg);
}
});
},
/**
* Show dialog to confirm pause
* @param {number} maintenanceID ID of maintenance to confirm
* pause.
* @returns {void}
*/
pauseDialog(maintenanceID) {
this.selectedMaintenanceID = maintenanceID;
@@ -173,6 +177,7 @@ export default {
/**
* Pause maintenance
* @returns {void}
*/
pauseMaintenance() {
this.$root.getSocket().emit("pauseMaintenance", this.selectedMaintenanceID, (res) => {
@@ -182,6 +187,8 @@ export default {
/**
* Resume maintenance
* @param {number} id ID of maintenance to resume
* @returns {void}
*/
resumeMaintenance(id) {
this.$root.getSocket().emit("resumeMaintenance", id, (res) => {

View File

@@ -45,7 +45,10 @@ export default {
},
methods: {
/** Go back 1 in browser history */
/**
* Go back 1 in browser history
* @returns {void}
*/
goBack() {
history.back();
}

View File

@@ -113,9 +113,6 @@ export default {
proxies: {
title: this.$t("Proxies"),
},
backup: {
title: this.$t("Backup"),
},
about: {
title: this.$t("About"),
},
@@ -139,6 +136,7 @@ export default {
/**
* Load the general settings page
* For desktop only, on mobile do nothing
* @returns {void}
*/
loadGeneralPage() {
if (!this.currentPage && !this.$root.isMobile) {
@@ -146,7 +144,10 @@ export default {
}
},
/** Load settings from server */
/**
* Load settings from server
* @returns {void}
*/
loadSettings() {
this.$root.getSocket().emit("getSettings", (res) => {
this.settings = res.data;
@@ -190,13 +191,15 @@ export default {
/**
* Callback for saving settings
* @callback saveSettingsCB
* @param {Object} res Result of operation
* @param {object} res Result of operation
* @returns {void}
*/
/**
* Save Settings
* @param {saveSettingsCB} [callback]
* @param {string} [currentPassword] Only need for disableAuth to true
* @param {saveSettingsCB} callback Callback for socket response
* @param {string} currentPassword Only need for disableAuth to true
* @returns {void}
*/
saveSettings(callback, currentPassword) {
let valid = this.validateSettings();
@@ -216,7 +219,7 @@ export default {
/**
* Ensure settings are valid
* @returns {Object} Contains success state and error msg
* @returns {object} Contains success state and error msg
*/
validateSettings() {
if (this.settings.keepDataPeriodDays < 0) {

View File

@@ -46,9 +46,6 @@
</template>
<script>
import { useToast } from "vue-toastification";
const toast = useToast();
export default {
data() {
return {
@@ -62,6 +59,8 @@ export default {
},
mounted() {
// TODO: Check if it is a database setup
this.$root.getSocket().emit("needSetup", (needSetup) => {
if (! needSetup) {
this.$router.push("/");
@@ -77,7 +76,7 @@ export default {
this.processing = true;
if (this.password !== this.repeatPassword) {
toast.error(this.$t("PasswordsDoNotMatch"));
this.$root.toastError("PasswordsDoNotMatch");
this.processing = false;
return;
}

238
src/pages/SetupDatabase.vue Normal file
View File

@@ -0,0 +1,238 @@
<template>
<div v-if="show" class="form-container">
<form @submit.prevent="submit">
<div>
<object width="64" height="64" data="/icon.svg" />
<div style="font-size: 28px; font-weight: bold; margin-top: 5px;">
Uptime Kuma
</div>
</div>
<div v-if="info.runningSetup" class="mt-5">
<div class="alert alert-success mx-3 px-4" role="alert">
<div class="d-flex align-items-center">
<strong>Setting up the database. It may take a while, please be patient.</strong>
<div class="ms-3 pt-1">
<div class="spinner-border" role="status" aria-hidden="true"></div>
</div>
</div>
</div>
</div>
<template v-if="!info.runningSetup">
<div class="form-floating short mt-3">
<select id="language" v-model="$root.language" class="form-select">
<option v-for="(lang, i) in $i18n.availableLocales" :key="`Lang${i}`" :value="lang">
{{ $i18n.messages[lang].languageName }}
</option>
</select>
<label for="language" class="form-label">{{ $t("Language") }}</label>
</div>
<p class="mt-5 short">
{{ $t("setupDatabaseChooseDatabase") }}
</p>
<div class="btn-group" role="group" aria-label="Basic radio toggle button group">
<template v-if="info.isEnabledEmbeddedMariaDB">
<input id="btnradio3" v-model="dbConfig.type" type="radio" class="btn-check" autocomplete="off" value="embedded-mariadb">
<label class="btn btn-outline-primary" for="btnradio3">
Embedded MariaDB
</label>
</template>
<input id="btnradio2" v-model="dbConfig.type" type="radio" class="btn-check" autocomplete="off" value="mariadb">
<label class="btn btn-outline-primary" for="btnradio2">
MariaDB/MySQL
</label>
<input id="btnradio1" v-model="dbConfig.type" type="radio" class="btn-check" autocomplete="off" value="sqlite">
<label class="btn btn-outline-primary" for="btnradio1">
SQLite
</label>
</div>
<div v-if="dbConfig.type === 'embedded-mariadb'" class="mt-3 short">
{{ $t("setupDatabaseEmbeddedMariaDB") }}
</div>
<div v-if="dbConfig.type === 'mariadb'" class="mt-3 short">
{{ $t("setupDatabaseMariaDB") }}
</div>
<div v-if="dbConfig.type === 'sqlite'" class="mt-3 short">
{{ $t("setupDatabaseSQLite") }}
</div>
<template v-if="dbConfig.type === 'mariadb'">
<div class="form-floating mt-3 short">
<input id="floatingInput" v-model="dbConfig.hostname" type="text" class="form-control" required>
<label for="floatingInput">{{ $t("Hostname") }}</label>
</div>
<div class="form-floating mt-3 short">
<input id="floatingInput" v-model="dbConfig.port" type="text" class="form-control" required>
<label for="floatingInput">{{ $t("Port") }}</label>
</div>
<div class="form-floating mt-3 short">
<input id="floatingInput" v-model="dbConfig.username" type="text" class="form-control" required>
<label for="floatingInput">{{ $t("Username") }}</label>
</div>
<div class="form-floating mt-3 short">
<input id="floatingInput" v-model="dbConfig.password" type="password" class="form-control" required>
<label for="floatingInput">{{ $t("Password") }}</label>
</div>
<div class="form-floating mt-3 short">
<input id="floatingInput" v-model="dbConfig.dbName" type="text" class="form-control" required>
<label for="floatingInput">{{ $t("dbName") }}</label>
</div>
</template>
<button class="btn btn-primary mt-4 short" type="submit" :disabled="disabledButton">
{{ $t("Next") }}
</button>
</template>
</form>
</div>
</template>
<script>
import axios from "axios";
import { useToast } from "vue-toastification";
import { sleep } from "../util.ts";
const toast = useToast();
export default {
data() {
return {
show: false,
dbConfig: {
type: undefined,
port: 3306,
hostname: "",
username: "",
password: "",
dbName: "kuma",
},
info: {
needSetup: false,
runningSetup: false,
isEnabledEmbeddedMariaDB: false,
},
};
},
computed: {
disabledButton() {
return this.dbConfig.type === undefined || this.info.runningSetup;
},
},
async mounted() {
let res = await axios.get("/setup-database-info");
this.info = res.data;
if (this.info && this.info.needSetup === false) {
location.href = "/setup";
} else {
this.show = true;
}
},
methods: {
async submit() {
this.info.runningSetup = true;
try {
await axios.post("/setup-database", {
dbConfig: this.dbConfig,
});
await sleep(2000);
await this.goToMainServerWhenReady();
} catch (e) {
toast.error(e.response.data);
} finally {
this.info.runningSetup = false;
}
},
async goToMainServerWhenReady() {
try {
console.log("Trying...");
let res = await axios.get("/setup-database-info");
if (res.data && res.data.needSetup === false) {
this.show = false;
location.href = "/setup";
} else {
if (res.data) {
this.info = res.data;
}
throw new Error("not ready");
}
} catch (e) {
console.log("Not ready yet");
await sleep(2000);
await this.goToMainServerWhenReady();
}
},
test() {
this.$root.toastError("not implemented");
}
},
};
</script>
<style lang="scss" scoped>
.form-container {
display: flex;
align-items: center;
justify-content: center;
padding-top: 40px;
padding-bottom: 40px;
}
.btn-group {
label {
width: 200px;
line-height: 55px;
font-size: 16px;
font-weight: bold;
}
}
.form-floating {
> .form-select {
padding-left: 1.3rem;
padding-top: 1.525rem;
line-height: 1.35;
~ label {
padding-left: 1.3rem;
}
}
> label {
padding-left: 1.3rem;
}
> .form-control {
padding-left: 1.3rem;
}
}
.short {
width: 300px;
}
form {
max-width: 800px;
text-align: center;
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
}
</style>

View File

@@ -453,6 +453,7 @@ export default {
/**
* If the monitor is added to public list, which will not be in this list.
* @returns {object[]} List of monitors
*/
sortedMonitorList() {
let result = [];
@@ -597,7 +598,8 @@ export default {
/**
* If connected to the socket and logged in, request private data of this statusPage
* @param connected
* @param {boolean} loggedIn Is the client logged in?
* @returns {void}
*/
"$root.loggedIn"(loggedIn) {
if (loggedIn) {
@@ -612,7 +614,7 @@ export default {
}
} else {
toast.error(res.msg);
this.$root.toastError(res.msg);
}
});
}
@@ -620,6 +622,8 @@ export default {
/**
* Selected a monitor and add to the list.
* @param {object} monitor Monitor to add
* @returns {void}
*/
selectedMonitor(monitor) {
if (monitor) {
@@ -726,7 +730,7 @@ export default {
/**
* Get status page data
* It should be preloaded in window.preloadData
* @returns {Promise<any>}
* @returns {Promise<any>} Status page data
*/
getData: function () {
if (window.preloadData) {
@@ -741,13 +745,16 @@ export default {
/**
* Provide syntax highlighting for CSS
* @param {string} code Text to highlight
* @returns {string}
* @returns {string} Highlighted CSS
*/
highlighter(code) {
return highlight(code, languages.css);
},
/** Update the heartbeat list and update favicon if neccessary */
/**
* Update the heartbeat list and update favicon if necessary
* @returns {void}
*/
updateHeartbeatList() {
// If editMode, it will use the data from websocket.
if (! this.editMode) {
@@ -795,7 +802,10 @@ export default {
}, 1000);
},
/** Enable editing mode */
/**
* Enable editing mode
* @returns {void}
*/
edit() {
if (this.hasToken) {
this.$root.initSocketIO(true);
@@ -807,7 +817,10 @@ export default {
}
},
/** Save the status page */
/**
* Save the status page
* @returns {void}
*/
save() {
this.loading = true;
let startTime = new Date();
@@ -838,33 +851,42 @@ export default {
});
},
/** Show dialog confirming deletion */
/**
* Show dialog confirming deletion
* @returns {void}
*/
deleteDialog() {
this.$refs.confirmDelete.show();
},
/** Request deletion of this status page */
/**
* Request deletion of this status page
* @returns {void}
*/
deleteStatusPage() {
this.$root.getSocket().emit("deleteStatusPage", this.slug, (res) => {
if (res.ok) {
this.enableEditMode = false;
location.href = "/manage-status-page";
} else {
toast.error(res.msg);
this.$root.toastError(res.msg);
}
});
},
/**
* Returns label for a specifed monitor
* @param {Object} monitor Object representing monitor
* @returns {string}
* Returns label for a specified monitor
* @param {object} monitor Object representing monitor
* @returns {string} Monitor label
*/
monitorSelectorLabel(monitor) {
return `${monitor.name}`;
},
/** Add a group to the status page */
/**
* Add a group to the status page
* @returns {void}
*/
addGroup() {
let groupName = this.$t("Untitled Group");
@@ -878,12 +900,18 @@ export default {
});
},
/** Add a domain to the status page */
/**
* Add a domain to the status page
* @returns {void}
*/
addDomainField() {
this.config.domainNameList.push("");
},
/** Discard changes to status page */
/**
* Discard changes to status page
* @returns {void}
*/
discard() {
location.href = "/status/" + this.slug;
},
@@ -891,19 +919,26 @@ export default {
/**
* Set URL of new image after successful crop operation
* @param {string} imgDataUrl URL of image in data:// format
* @returns {void}
*/
cropSuccess(imgDataUrl) {
this.imgDataUrl = imgDataUrl;
},
/** Show image crop dialog if in edit mode */
/**
* Show image crop dialog if in edit mode
* @returns {void}
*/
showImageCropUploadMethod() {
if (this.editMode) {
this.showImageCropUpload = true;
}
},
/** Create an incident for this status page */
/**
* Create an incident for this status page
* @returns {void}
*/
createIncident() {
this.enableEditIncidentMode = true;
@@ -918,10 +953,13 @@ export default {
};
},
/** Post the incident to the status page */
/**
* Post the incident to the status page
* @returns {void}
*/
postIncident() {
if (this.incident.title === "" || this.incident.content === "") {
toast.error(this.$t("Please input title and content"));
this.$root.toastError("Please input title and content");
return;
}
@@ -931,20 +969,26 @@ export default {
this.enableEditIncidentMode = false;
this.incident = res.incident;
} else {
toast.error(res.msg);
this.$root.toastError(res.msg);
}
});
},
/** Click Edit Button */
/**
* Click Edit Button
* @returns {void}
*/
editIncident() {
this.enableEditIncidentMode = true;
this.previousIncident = Object.assign({}, this.incident);
},
/** Cancel creation or editing of incident */
/**
* Cancel creation or editing of incident
* @returns {void}
*/
cancelIncident() {
this.enableEditIncidentMode = false;
@@ -954,7 +998,10 @@ export default {
}
},
/** Unpin the incident */
/**
* Unpin the incident
* @returns {void}
*/
unpinIncident() {
this.$root.getSocket().emit("unpinIncident", this.slug, () => {
this.incident = null;
@@ -963,7 +1010,8 @@ export default {
/**
* Get the relative time difference of a date from now
* @returns {string}
* @param {any} date Date to get time difference
* @returns {string} Time difference
*/
dateFromNow(date) {
return dayjs.utc(date).fromNow();
@@ -972,6 +1020,7 @@ export default {
/**
* Remove a domain from the status page
* @param {number} index Index of domain to remove
* @returns {void}
*/
removeDomain(index) {
this.config.domainNameList.splice(index, 1);
@@ -979,7 +1028,7 @@ export default {
/**
* Generate sanitized HTML from maintenance description
* @param {string} description
* @param {string} description Text to sanitize
* @returns {string} Sanitized HTML
*/
maintenanceHTML(description) {
@@ -1196,20 +1245,6 @@ footer {
}
}
/* required class */
.css-editor {
/* we dont use `language-` classes anymore so thats why we need to add background and text color manually */
border-radius: 1rem;
padding: 10px 5px;
border: 1px solid #ced4da;
.dark & {
background: $dark-bg;
border: 1px solid $dark-border-color;
}
}
.bg-maintenance {
.alert-heading {
font-weight: bold;

View File

@@ -19,6 +19,7 @@ import DockerHosts from "./components/settings/Docker.vue";
import MaintenanceDetails from "./pages/MaintenanceDetails.vue";
import ManageMaintenance from "./pages/ManageMaintenance.vue";
import APIKeys from "./components/settings/APIKeys.vue";
import SetupDatabase from "./pages/SetupDatabase.vue";
// Settings - Sub Pages
import Appearance from "./components/settings/Appearance.vue";
@@ -29,7 +30,6 @@ import Tags from "./components/settings/Tags.vue";
import MonitorHistory from "./components/settings/MonitorHistory.vue";
const Security = () => import("./components/settings/Security.vue");
import Proxies from "./components/settings/Proxies.vue";
import Backup from "./components/settings/Backup.vue";
import About from "./components/settings/About.vue";
const routes = [
@@ -125,10 +125,6 @@ const routes = [
path: "proxies",
component: Proxies,
},
{
path: "backup",
component: Backup,
},
{
path: "about",
component: About,
@@ -167,6 +163,10 @@ const routes = [
path: "/setup",
component: Setup,
},
{
path: "/setup-database",
component: SetupDatabase,
},
{
path: "/status-page",
component: StatusPage,

View File

@@ -1,9 +1,11 @@
import dayjs from "dayjs";
import timezones from "timezones-list";
import { localeDirection, currentLocale } from "./i18n";
import { POSITION } from "vue-toastification";
/**
* Returns the offset from UTC in hours for the current locale.
* @param {string} timeZone Timezone to get offset for
* @returns {number} The offset from UTC in hours.
*
* Generated by Trelent
@@ -20,12 +22,11 @@ function getTimezoneOffset(timeZone) {
}
/**
* Returns a list of timezones sorted by their offset from UTC.
* @param {Object[]} timezones An array of timezone objects.
* @returns {Object[]} A list of the given timezones sorted by their offset from UTC.
*
* Generated by Trelent
*/
* Returns a list of timezones sorted by their offset from UTC.
* @returns {object[]} A list of the given timezones sorted by their offset from UTC.
*
* Generated by Trelent
*/
export function timezoneList() {
let result = [];
@@ -58,7 +59,10 @@ export function timezoneList() {
return result;
}
/** Set the locale of the HTML page */
/**
* Set the locale of the HTML page
* @returns {void}
*/
export function setPageLocale() {
const html = document.documentElement;
html.setAttribute("lang", currentLocale() );
@@ -68,7 +72,7 @@ export function setPageLocale() {
/**
* Get the base URL
* Mainly used for dev, because the backend and the frontend are in different ports.
* @returns {string}
* @returns {string} Base URL
*/
export function getResBaseURL() {
const env = process.env.NODE_ENV;
@@ -81,6 +85,10 @@ export function getResBaseURL() {
}
}
/**
* Are we currently running in a dev container?
* @returns {boolean} Running in dev container?
*/
export function isDevContainer() {
// eslint-disable-next-line no-undef
return (typeof DEVCONTAINER === "string" && DEVCONTAINER === "1");
@@ -88,6 +96,7 @@ export function isDevContainer() {
/**
* Supports GitHub Codespaces only currently
* @returns {string} Dev container server hostname
*/
export function getDevContainerServerHostname() {
if (!isDevContainer()) {
@@ -99,9 +108,10 @@ export function getDevContainerServerHostname() {
}
/**
*
* @param {} mqtt wheather or not the regex should take into account the fact that it is an mqtt uri
* @returns RegExp The requested regex
* Regex pattern fr identifying hostnames and IP addresses
* @param {boolean} mqtt whether or not the regex should take into
* account the fact that it is an mqtt uri
* @returns {RegExp} The requested regex
*/
export function hostNameRegexPattern(mqtt = false) {
// mqtt, mqtts, ws and wss schemes accepted by mqtt.js (https://github.com/mqttjs/MQTT.js/#connect)
@@ -117,7 +127,8 @@ export function hostNameRegexPattern(mqtt = false) {
/**
* Get the tag color options
* Shared between components
* @returns {Object[]}
* @param {any} self Component
* @returns {object[]} Colour options
*/
export function colorOptions(self) {
return [
@@ -139,3 +150,65 @@ export function colorOptions(self) {
color: "#DB2777" },
];
}
/**
* Loads the toast timeout settings from storage.
* @returns {object} The toast plugin options object.
*/
export function loadToastSettings() {
return {
position: POSITION.BOTTOM_RIGHT,
containerClassName: "toast-container mb-5",
showCloseButtonOnHover: true,
filterBeforeCreate: (toast, toasts) => {
if (toast.timeout === 0) {
return false;
} else {
return toast;
}
},
};
}
/**
* Get timeout for success toasts
* @returns {(number|boolean)} Timeout in ms. If false timeout disabled.
*/
export function getToastSuccessTimeout() {
let successTimeout = 20000;
if (localStorage.toastSuccessTimeout !== undefined) {
const parsedTimeout = parseInt(localStorage.toastSuccessTimeout);
if (parsedTimeout != null && !Number.isNaN(parsedTimeout)) {
successTimeout = parsedTimeout;
}
}
if (successTimeout === -1) {
successTimeout = false;
}
return successTimeout;
}
/**
* Get timeout for error toasts
* @returns {(number|boolean)} Timeout in ms. If false timeout disabled.
*/
export function getToastErrorTimeout() {
let errorTimeout = -1;
if (localStorage.toastErrorTimeout !== undefined) {
const parsedTimeout = parseInt(localStorage.toastErrorTimeout);
if (parsedTimeout != null && !Number.isNaN(parsedTimeout)) {
errorTimeout = parsedTimeout;
}
}
if (errorTimeout === -1) {
errorTimeout = false;
}
return errorTimeout;
}

View File

@@ -1,13 +1,16 @@
"use strict";
/*!
// Common Util for frontend and backend
//
// DOT NOT MODIFY util.js!
// Need to run "tsc" to compile if there are any changes.
// Need to run "npm run tsc" to compile if there are any changes.
//
// Backend uses the compiled file util.js
// Frontend uses util.ts
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.localToUTC = exports.utcToLocal = exports.utcToISODateTime = exports.isoToUTCDateTime = exports.parseTimeFromTimeObject = exports.parseTimeObject = exports.getMaintenanceRelativeURL = exports.getMonitorRelativeURL = exports.genSecret = exports.getCryptoRandomInt = exports.getRandomInt = exports.getRandomArbitrary = exports.TimeLogger = exports.polyfill = exports.log = exports.debug = exports.ucfirst = exports.sleep = exports.flipStatus = exports.MIN_INTERVAL_SECOND = exports.MAX_INTERVAL_SECOND = exports.SQL_DATETIME_FORMAT_WITHOUT_SECOND = exports.SQL_DATETIME_FORMAT = exports.SQL_DATE_FORMAT = exports.STATUS_PAGE_MAINTENANCE = exports.STATUS_PAGE_PARTIAL_DOWN = exports.STATUS_PAGE_ALL_UP = exports.STATUS_PAGE_ALL_DOWN = exports.MAINTENANCE = exports.PENDING = exports.UP = exports.DOWN = exports.appName = exports.isDev = void 0;
exports.debug = exports.ucfirst = exports.sleep = exports.flipStatus = exports.CONSOLE_STYLE_BgGray = exports.CONSOLE_STYLE_BgWhite = exports.CONSOLE_STYLE_BgCyan = exports.CONSOLE_STYLE_BgMagenta = exports.CONSOLE_STYLE_BgBlue = exports.CONSOLE_STYLE_BgYellow = exports.CONSOLE_STYLE_BgGreen = exports.CONSOLE_STYLE_BgRed = exports.CONSOLE_STYLE_BgBlack = exports.CONSOLE_STYLE_FgPink = exports.CONSOLE_STYLE_FgBrown = exports.CONSOLE_STYLE_FgViolet = exports.CONSOLE_STYLE_FgLightBlue = exports.CONSOLE_STYLE_FgLightGreen = exports.CONSOLE_STYLE_FgOrange = exports.CONSOLE_STYLE_FgGray = exports.CONSOLE_STYLE_FgWhite = exports.CONSOLE_STYLE_FgCyan = exports.CONSOLE_STYLE_FgMagenta = exports.CONSOLE_STYLE_FgBlue = exports.CONSOLE_STYLE_FgYellow = exports.CONSOLE_STYLE_FgGreen = exports.CONSOLE_STYLE_FgRed = exports.CONSOLE_STYLE_FgBlack = exports.CONSOLE_STYLE_Hidden = exports.CONSOLE_STYLE_Reverse = exports.CONSOLE_STYLE_Blink = exports.CONSOLE_STYLE_Underscore = exports.CONSOLE_STYLE_Dim = exports.CONSOLE_STYLE_Bright = exports.CONSOLE_STYLE_Reset = exports.MIN_INTERVAL_SECOND = exports.MAX_INTERVAL_SECOND = exports.SQL_DATETIME_FORMAT_WITHOUT_SECOND = exports.SQL_DATETIME_FORMAT = exports.SQL_DATE_FORMAT = exports.STATUS_PAGE_MAINTENANCE = exports.STATUS_PAGE_PARTIAL_DOWN = exports.STATUS_PAGE_ALL_UP = exports.STATUS_PAGE_ALL_DOWN = exports.MAINTENANCE = exports.PENDING = exports.UP = exports.DOWN = exports.appName = exports.isDev = void 0;
exports.intHash = exports.localToUTC = exports.utcToLocal = exports.utcToISODateTime = exports.isoToUTCDateTime = exports.parseTimeFromTimeObject = exports.parseTimeObject = exports.getMaintenanceRelativeURL = exports.getMonitorRelativeURL = exports.genSecret = exports.getCryptoRandomInt = exports.getRandomInt = exports.getRandomArbitrary = exports.TimeLogger = exports.polyfill = exports.log = void 0;
const dayjs = require("dayjs");
exports.isDev = process.env.NODE_ENV === "development";
exports.appName = "Uptime Kuma";
@@ -22,9 +25,57 @@ exports.STATUS_PAGE_MAINTENANCE = 3;
exports.SQL_DATE_FORMAT = "YYYY-MM-DD";
exports.SQL_DATETIME_FORMAT = "YYYY-MM-DD HH:mm:ss";
exports.SQL_DATETIME_FORMAT_WITHOUT_SECOND = "YYYY-MM-DD HH:mm";
exports.MAX_INTERVAL_SECOND = 2073600; // 24 days
exports.MIN_INTERVAL_SECOND = 20; // 20 seconds
/** Flip the status of s */
exports.MAX_INTERVAL_SECOND = 2073600;
exports.MIN_INTERVAL_SECOND = 20;
exports.CONSOLE_STYLE_Reset = "\x1b[0m";
exports.CONSOLE_STYLE_Bright = "\x1b[1m";
exports.CONSOLE_STYLE_Dim = "\x1b[2m";
exports.CONSOLE_STYLE_Underscore = "\x1b[4m";
exports.CONSOLE_STYLE_Blink = "\x1b[5m";
exports.CONSOLE_STYLE_Reverse = "\x1b[7m";
exports.CONSOLE_STYLE_Hidden = "\x1b[8m";
exports.CONSOLE_STYLE_FgBlack = "\x1b[30m";
exports.CONSOLE_STYLE_FgRed = "\x1b[31m";
exports.CONSOLE_STYLE_FgGreen = "\x1b[32m";
exports.CONSOLE_STYLE_FgYellow = "\x1b[33m";
exports.CONSOLE_STYLE_FgBlue = "\x1b[34m";
exports.CONSOLE_STYLE_FgMagenta = "\x1b[35m";
exports.CONSOLE_STYLE_FgCyan = "\x1b[36m";
exports.CONSOLE_STYLE_FgWhite = "\x1b[37m";
exports.CONSOLE_STYLE_FgGray = "\x1b[90m";
exports.CONSOLE_STYLE_FgOrange = "\x1b[38;5;208m";
exports.CONSOLE_STYLE_FgLightGreen = "\x1b[38;5;119m";
exports.CONSOLE_STYLE_FgLightBlue = "\x1b[38;5;117m";
exports.CONSOLE_STYLE_FgViolet = "\x1b[38;5;141m";
exports.CONSOLE_STYLE_FgBrown = "\x1b[38;5;130m";
exports.CONSOLE_STYLE_FgPink = "\x1b[38;5;219m";
exports.CONSOLE_STYLE_BgBlack = "\x1b[40m";
exports.CONSOLE_STYLE_BgRed = "\x1b[41m";
exports.CONSOLE_STYLE_BgGreen = "\x1b[42m";
exports.CONSOLE_STYLE_BgYellow = "\x1b[43m";
exports.CONSOLE_STYLE_BgBlue = "\x1b[44m";
exports.CONSOLE_STYLE_BgMagenta = "\x1b[45m";
exports.CONSOLE_STYLE_BgCyan = "\x1b[46m";
exports.CONSOLE_STYLE_BgWhite = "\x1b[47m";
exports.CONSOLE_STYLE_BgGray = "\x1b[100m";
const consoleModuleColors = [
exports.CONSOLE_STYLE_FgCyan,
exports.CONSOLE_STYLE_FgGreen,
exports.CONSOLE_STYLE_FgLightGreen,
exports.CONSOLE_STYLE_FgBlue,
exports.CONSOLE_STYLE_FgLightBlue,
exports.CONSOLE_STYLE_FgMagenta,
exports.CONSOLE_STYLE_FgOrange,
exports.CONSOLE_STYLE_FgViolet,
exports.CONSOLE_STYLE_FgBrown,
exports.CONSOLE_STYLE_FgPink,
];
const consoleLevelColors = {
"INFO": exports.CONSOLE_STYLE_FgCyan,
"WARN": exports.CONSOLE_STYLE_FgYellow,
"ERROR": exports.CONSOLE_STYLE_FgRed,
"DEBUG": exports.CONSOLE_STYLE_FgGray,
};
function flipStatus(s) {
if (s === exports.UP) {
return exports.DOWN;
@@ -35,18 +86,10 @@ function flipStatus(s) {
return s;
}
exports.flipStatus = flipStatus;
/**
* Delays for specified number of seconds
* @param ms Number of milliseconds to sleep for
*/
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
exports.sleep = sleep;
/**
* PHP's ucfirst
* @param str
*/
function ucfirst(str) {
if (!str) {
return str;
@@ -55,26 +98,12 @@ function ucfirst(str) {
return firstLetter.toUpperCase() + str.substr(1);
}
exports.ucfirst = ucfirst;
/**
* @deprecated Use log.debug
* @since https://github.com/louislam/uptime-kuma/pull/910
* @param msg
*/
function debug(msg) {
exports.log.log("", msg, "debug");
}
exports.debug = debug;
class Logger {
constructor() {
/**
* UPTIME_KUMA_HIDE_LOG=debug_monitor,info_monitor
*
* Example:
* [
* "debug_monitor", // Hide all logs that level is debug and the module is monitor
* "info_monitor",
* ]
*/
this.hideLog = {
info: [],
warn: [],
@@ -82,10 +111,9 @@ class Logger {
debug: [],
};
if (typeof process !== "undefined" && process.env.UPTIME_KUMA_HIDE_LOG) {
let list = process.env.UPTIME_KUMA_HIDE_LOG.split(",").map(v => v.toLowerCase());
for (let pair of list) {
// split first "_" only
let values = pair.split(/_(.*)/s);
const list = process.env.UPTIME_KUMA_HIDE_LOG.split(",").map(v => v.toLowerCase());
for (const pair of list) {
const values = pair.split(/_(.*)/s);
if (values.length >= 2) {
this.hideLog[values[0]].push(values[1]);
}
@@ -94,12 +122,6 @@ class Logger {
this.debug("server", this.hideLog);
}
}
/**
* Write a message to the log
* @param module The module the log comes from
* @param msg Message to write
* @param level Log level. One of INFO, WARN, ERROR, DEBUG or can be customized.
*/
log(module, msg, level) {
if (this.hideLog[level] && this.hideLog[level].includes(module.toLowerCase())) {
return;
@@ -113,63 +135,56 @@ class Logger {
else {
now = dayjs().format();
}
const formattedMessage = (typeof msg === "string") ? `${now} [${module}] ${level}: ${msg}` : msg;
const levelColor = consoleLevelColors[level];
const moduleColor = consoleModuleColors[intHash(module, consoleModuleColors.length)];
let timePart = exports.CONSOLE_STYLE_FgCyan + now + exports.CONSOLE_STYLE_Reset;
let modulePart = "[" + moduleColor + module + exports.CONSOLE_STYLE_Reset + "]";
let levelPart = levelColor + `${level}:` + exports.CONSOLE_STYLE_Reset;
if (level === "INFO") {
console.info(formattedMessage);
console.info(timePart, modulePart, levelPart, msg);
}
else if (level === "WARN") {
console.warn(formattedMessage);
console.warn(timePart, modulePart, levelPart, msg);
}
else if (level === "ERROR") {
console.error(formattedMessage);
let msgPart;
if (typeof msg === "string") {
msgPart = exports.CONSOLE_STYLE_FgRed + msg + exports.CONSOLE_STYLE_Reset;
}
else {
msgPart = msg;
}
console.error(timePart, modulePart, levelPart, msgPart);
}
else if (level === "DEBUG") {
if (exports.isDev) {
console.log(formattedMessage);
timePart = exports.CONSOLE_STYLE_FgGray + now + exports.CONSOLE_STYLE_Reset;
let msgPart;
if (typeof msg === "string") {
msgPart = exports.CONSOLE_STYLE_FgGray + msg + exports.CONSOLE_STYLE_Reset;
}
else {
msgPart = msg;
}
console.debug(timePart, modulePart, levelPart, msgPart);
}
}
else {
console.log(formattedMessage);
console.log(timePart, modulePart, msg);
}
}
/**
* Log an INFO message
* @param module Module log comes from
* @param msg Message to write
*/
info(module, msg) {
this.log(module, msg, "info");
}
/**
* Log a WARN message
* @param module Module log comes from
* @param msg Message to write
*/
warn(module, msg) {
this.log(module, msg, "warn");
}
/**
* Log an ERROR message
* @param module Module log comes from
* @param msg Message to write
*/
error(module, msg) {
this.log(module, msg, "error");
}
/**
* Log a DEBUG message
* @param module Module log comes from
* @param msg Message to write
*/
debug(module, msg) {
this.log(module, msg, "debug");
}
/**
* Log an exeption as an ERROR
* @param module Module log comes from
* @param exception The exeption to include
* @param msg The message to write
*/
exception(module, exception, msg) {
let finalMessage = exception;
if (msg) {
@@ -179,20 +194,12 @@ class Logger {
}
}
exports.log = new Logger();
/**
* String.prototype.replaceAll() polyfill
* https://gomakethings.com/how-to-replace-a-section-of-a-string-with-another-one-with-vanilla-js/
* @author Chris Ferdinandi
* @license MIT
*/
function polyfill() {
if (!String.prototype.replaceAll) {
String.prototype.replaceAll = function (str, newStr) {
// If a regex pattern
if (Object.prototype.toString.call(str).toLowerCase() === "[object regexp]") {
return this.replace(str, newStr);
}
// If a string
return this.replace(new RegExp(str, "g"), newStr);
};
}
@@ -202,10 +209,6 @@ class TimeLogger {
constructor() {
this.startTime = dayjs().valueOf();
}
/**
* Output time since start of monitor
* @param name Name of monitor
*/
print(name) {
if (exports.isDev && process.env.TIMELOGGER === "1") {
console.log(name + ": " + (dayjs().valueOf() - this.startTime) + "ms");
@@ -213,66 +216,42 @@ class TimeLogger {
}
}
exports.TimeLogger = TimeLogger;
/**
* Returns a random number between min (inclusive) and max (exclusive)
*/
function getRandomArbitrary(min, max) {
return Math.random() * (max - min) + min;
}
exports.getRandomArbitrary = getRandomArbitrary;
/**
* From: https://stackoverflow.com/questions/1527803/generating-random-whole-numbers-in-javascript-in-a-specific-range
*
* Returns a random integer between min (inclusive) and max (inclusive).
* The value is no lower than min (or the next integer greater than min
* if min isn't an integer) and no greater than max (or the next integer
* lower than max if max isn't an integer).
* Using Math.round() will give you a non-uniform distribution!
*/
function getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min;
}
exports.getRandomInt = getRandomInt;
/**
* Returns either the NodeJS crypto.randomBytes() function or its
* browser equivalent implemented via window.crypto.getRandomValues()
*/
let getRandomBytes = ((typeof window !== 'undefined' && window.crypto)
// Browsers
const getRandomBytes = ((typeof window !== "undefined" && window.crypto)
? function () {
return (numBytes) => {
let randomBytes = new Uint8Array(numBytes);
const randomBytes = new Uint8Array(numBytes);
for (let i = 0; i < numBytes; i += 65536) {
window.crypto.getRandomValues(randomBytes.subarray(i, i + Math.min(numBytes - i, 65536)));
}
return randomBytes;
};
}
// Node
: function () {
return require("crypto").randomBytes;
})();
/**
* Get a random integer suitable for use in cryptography between upper
* and lower bounds.
* @param min Minimum value of integer
* @param max Maximum value of integer
* @returns Cryptographically suitable random integer
*/
function getCryptoRandomInt(min, max) {
// synchronous version of: https://github.com/joepie91/node-random-number-csprng
const range = max - min;
if (range >= Math.pow(2, 32))
if (range >= Math.pow(2, 32)) {
console.log("Warning! Range is too large.");
}
let tmpRange = range;
let bitsNeeded = 0;
let bytesNeeded = 0;
let mask = 1;
while (tmpRange > 0) {
if (bitsNeeded % 8 === 0)
if (bitsNeeded % 8 === 0) {
bytesNeeded += 1;
}
bitsNeeded += 1;
mask = mask << 1 | 1;
tmpRange = tmpRange >>> 1;
@@ -291,11 +270,6 @@ function getCryptoRandomInt(min, max) {
}
}
exports.getCryptoRandomInt = getCryptoRandomInt;
/**
* Generate a random alphanumeric string of fixed length
* @param length Length of string to generate
* @returns string
*/
function genSecret(length = 64) {
let secret = "";
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
@@ -306,29 +280,14 @@ function genSecret(length = 64) {
return secret;
}
exports.genSecret = genSecret;
/**
* Get the path of a monitor
* @param id ID of monitor
* @returns Formatted relative path
*/
function getMonitorRelativeURL(id) {
return "/dashboard/" + id;
}
exports.getMonitorRelativeURL = getMonitorRelativeURL;
/**
* Get relative path for maintenance
* @param id ID of maintenance
* @returns Formatted relative path
*/
function getMaintenanceRelativeURL(id) {
return "/maintenance/" + id;
}
exports.getMaintenanceRelativeURL = getMaintenanceRelativeURL;
/**
* Parse to Time Object that used in VueDatePicker
* @param {string} time E.g. 12:00
* @returns object
*/
function parseTimeObject(time) {
if (!time) {
return {
@@ -336,11 +295,11 @@ function parseTimeObject(time) {
minutes: 0,
};
}
let array = time.split(":");
const array = time.split(":");
if (array.length < 2) {
throw new Error("parseVueDatePickerTimeFormat: Invalid Time");
}
let obj = {
const obj = {
hours: parseInt(array[0]),
minutes: parseInt(array[1]),
seconds: 0,
@@ -351,9 +310,6 @@ function parseTimeObject(time) {
return obj;
}
exports.parseTimeObject = parseTimeObject;
/**
* @returns string e.g. 12:00
*/
function parseTimeFromTimeObject(obj) {
if (!obj) {
return obj;
@@ -366,36 +322,27 @@ function parseTimeFromTimeObject(obj) {
return result;
}
exports.parseTimeFromTimeObject = parseTimeFromTimeObject;
/**
* Convert ISO date to UTC
* @param input Date
* @returns ISO Date time
*/
function isoToUTCDateTime(input) {
return dayjs(input).utc().format(exports.SQL_DATETIME_FORMAT);
}
exports.isoToUTCDateTime = isoToUTCDateTime;
/**
* @param input
*/
function utcToISODateTime(input) {
return dayjs.utc(input).toISOString();
}
exports.utcToISODateTime = utcToISODateTime;
/**
* For SQL_DATETIME_FORMAT
*/
function utcToLocal(input, format = exports.SQL_DATETIME_FORMAT) {
return dayjs.utc(input).local().format(format);
}
exports.utcToLocal = utcToLocal;
/**
* Convert local datetime to UTC
* @param input Local date
* @param format Format to return
* @returns Date in requested format
*/
function localToUTC(input, format = exports.SQL_DATETIME_FORMAT) {
return dayjs(input).utc().format(format);
}
exports.localToUTC = localToUTC;
function intHash(str, length = 10) {
let hash = 0;
for (let i = 0; i < str.length; i++) {
hash += str.charCodeAt(i);
}
return (hash % length + length) % length;
}
exports.intHash = intHash;

View File

@@ -1,13 +1,19 @@
/*!
// Common Util for frontend and backend
//
// DOT NOT MODIFY util.js!
// Need to run "tsc" to compile if there are any changes.
// Need to run "npm run tsc" to compile if there are any changes.
//
// Backend uses the compiled file util.js
// Frontend uses util.ts
*/
import * as dayjs from "dayjs";
import * as dayjs from "dayjs";
// For loading dayjs plugins, don't remove event though it is not used in this file
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import * as timezone from "dayjs/plugin/timezone";
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import * as utc from "dayjs/plugin/utc";
export const isDev = process.env.NODE_ENV === "development";
@@ -29,7 +35,66 @@ export const SQL_DATETIME_FORMAT_WITHOUT_SECOND = "YYYY-MM-DD HH:mm";
export const MAX_INTERVAL_SECOND = 2073600; // 24 days
export const MIN_INTERVAL_SECOND = 20; // 20 seconds
/** Flip the status of s */
// Console colors
// https://stackoverflow.com/questions/9781218/how-to-change-node-jss-console-font-color
export const CONSOLE_STYLE_Reset = "\x1b[0m";
export const CONSOLE_STYLE_Bright = "\x1b[1m";
export const CONSOLE_STYLE_Dim = "\x1b[2m";
export const CONSOLE_STYLE_Underscore = "\x1b[4m";
export const CONSOLE_STYLE_Blink = "\x1b[5m";
export const CONSOLE_STYLE_Reverse = "\x1b[7m";
export const CONSOLE_STYLE_Hidden = "\x1b[8m";
export const CONSOLE_STYLE_FgBlack = "\x1b[30m";
export const CONSOLE_STYLE_FgRed = "\x1b[31m";
export const CONSOLE_STYLE_FgGreen = "\x1b[32m";
export const CONSOLE_STYLE_FgYellow = "\x1b[33m";
export const CONSOLE_STYLE_FgBlue = "\x1b[34m";
export const CONSOLE_STYLE_FgMagenta = "\x1b[35m";
export const CONSOLE_STYLE_FgCyan = "\x1b[36m";
export const CONSOLE_STYLE_FgWhite = "\x1b[37m";
export const CONSOLE_STYLE_FgGray = "\x1b[90m";
export const CONSOLE_STYLE_FgOrange = "\x1b[38;5;208m";
export const CONSOLE_STYLE_FgLightGreen = "\x1b[38;5;119m";
export const CONSOLE_STYLE_FgLightBlue = "\x1b[38;5;117m";
export const CONSOLE_STYLE_FgViolet = "\x1b[38;5;141m";
export const CONSOLE_STYLE_FgBrown = "\x1b[38;5;130m";
export const CONSOLE_STYLE_FgPink = "\x1b[38;5;219m";
export const CONSOLE_STYLE_BgBlack = "\x1b[40m";
export const CONSOLE_STYLE_BgRed = "\x1b[41m";
export const CONSOLE_STYLE_BgGreen = "\x1b[42m";
export const CONSOLE_STYLE_BgYellow = "\x1b[43m";
export const CONSOLE_STYLE_BgBlue = "\x1b[44m";
export const CONSOLE_STYLE_BgMagenta = "\x1b[45m";
export const CONSOLE_STYLE_BgCyan = "\x1b[46m";
export const CONSOLE_STYLE_BgWhite = "\x1b[47m";
export const CONSOLE_STYLE_BgGray = "\x1b[100m";
const consoleModuleColors = [
CONSOLE_STYLE_FgCyan,
CONSOLE_STYLE_FgGreen,
CONSOLE_STYLE_FgLightGreen,
CONSOLE_STYLE_FgBlue,
CONSOLE_STYLE_FgLightBlue,
CONSOLE_STYLE_FgMagenta,
CONSOLE_STYLE_FgOrange,
CONSOLE_STYLE_FgViolet,
CONSOLE_STYLE_FgBrown,
CONSOLE_STYLE_FgPink,
];
const consoleLevelColors : Record<string, string> = {
"INFO": CONSOLE_STYLE_FgCyan,
"WARN": CONSOLE_STYLE_FgYellow,
"ERROR": CONSOLE_STYLE_FgRed,
"DEBUG": CONSOLE_STYLE_FgGray,
};
/**
* Flip the status of s
* @param s
*/
export function flipStatus(s: number) {
if (s === UP) {
return DOWN;
@@ -64,11 +129,10 @@ export function ucfirst(str: string) {
}
/**
* @deprecated Use log.debug
* @since https://github.com/louislam/uptime-kuma/pull/910
* @deprecated Use log.debug (https://github.com/louislam/uptime-kuma/pull/910)
* @param msg
*/
export function debug(msg: any) {
export function debug(msg: unknown) {
log.log("", msg, "debug");
}
@@ -83,20 +147,23 @@ class Logger {
* "info_monitor",
* ]
*/
hideLog : any = {
hideLog : Record<string, string[]> = {
info: [],
warn: [],
error: [],
debug: [],
};
/**
*
*/
constructor() {
if (typeof process !== "undefined" && process.env.UPTIME_KUMA_HIDE_LOG) {
let list = process.env.UPTIME_KUMA_HIDE_LOG.split(",").map(v => v.toLowerCase());
const list = process.env.UPTIME_KUMA_HIDE_LOG.split(",").map(v => v.toLowerCase());
for (let pair of list) {
for (const pair of list) {
// split first "_" only
let values = pair.split(/_(.*)/s);
const values = pair.split(/_(.*)/s);
if (values.length >= 2) {
this.hideLog[values[0]].push(values[1]);
@@ -128,20 +195,39 @@ class Logger {
} else {
now = dayjs().format();
}
const formattedMessage = (typeof msg === "string") ? `${now} [${module}] ${level}: ${msg}` : msg;
const levelColor = consoleLevelColors[level];
const moduleColor = consoleModuleColors[intHash(module, consoleModuleColors.length)];
let timePart = CONSOLE_STYLE_FgCyan + now + CONSOLE_STYLE_Reset;
let modulePart = "[" + moduleColor + module + CONSOLE_STYLE_Reset + "]";
let levelPart = levelColor + `${level}:` + CONSOLE_STYLE_Reset;
if (level === "INFO") {
console.info(formattedMessage);
console.info(timePart, modulePart, levelPart, msg);
} else if (level === "WARN") {
console.warn(formattedMessage);
console.warn(timePart, modulePart, levelPart, msg);
} else if (level === "ERROR") {
console.error(formattedMessage);
let msgPart :string;
if (typeof msg === "string") {
msgPart = CONSOLE_STYLE_FgRed + msg + CONSOLE_STYLE_Reset;
} else {
msgPart = msg;
}
console.error(timePart, modulePart, levelPart, msgPart);
} else if (level === "DEBUG") {
if (isDev) {
console.log(formattedMessage);
timePart = CONSOLE_STYLE_FgGray + now + CONSOLE_STYLE_Reset;
let msgPart :string;
if (typeof msg === "string") {
msgPart = CONSOLE_STYLE_FgGray + msg + CONSOLE_STYLE_Reset;
} else {
msgPart = msg;
}
console.debug(timePart, modulePart, levelPart, msgPart);
}
} else {
console.log(formattedMessage);
console.log(timePart, modulePart, msg);
}
}
@@ -150,7 +236,7 @@ class Logger {
* @param module Module log comes from
* @param msg Message to write
*/
info(module: string, msg: any) {
info(module: string, msg: unknown) {
this.log(module, msg, "info");
}
@@ -159,7 +245,7 @@ class Logger {
* @param module Module log comes from
* @param msg Message to write
*/
warn(module: string, msg: any) {
warn(module: string, msg: unknown) {
this.log(module, msg, "warn");
}
@@ -168,8 +254,8 @@ class Logger {
* @param module Module log comes from
* @param msg Message to write
*/
error(module: string, msg: any) {
this.log(module, msg, "error");
error(module: string, msg: unknown) {
this.log(module, msg, "error");
}
/**
@@ -177,24 +263,24 @@ class Logger {
* @param module Module log comes from
* @param msg Message to write
*/
debug(module: string, msg: any) {
this.log(module, msg, "debug");
debug(module: string, msg: unknown) {
this.log(module, msg, "debug");
}
/**
* Log an exeption as an ERROR
* Log an exception as an ERROR
* @param module Module log comes from
* @param exception The exeption to include
* @param exception The exception to include
* @param msg The message to write
*/
exception(module: string, exception: any, msg: any) {
let finalMessage = exception
exception(module: string, exception: unknown, msg: unknown) {
let finalMessage = exception;
if (msg) {
finalMessage = `${msg}: ${exception}`
finalMessage = `${msg}: ${exception}`;
}
this.log(module, finalMessage , "error");
this.log(module, finalMessage, "error");
}
}
@@ -225,22 +311,28 @@ export function polyfill() {
export class TimeLogger {
startTime: number;
/**
*
*/
constructor() {
this.startTime = dayjs().valueOf();
}
/**
* Output time since start of monitor
* @param name Name of monitor
*/
print(name: string) {
if (isDev && process.env.TIMELOGGER === "1") {
console.log(name + ": " + (dayjs().valueOf() - this.startTime) + "ms")
console.log(name + ": " + (dayjs().valueOf() - this.startTime) + "ms");
}
}
}
/**
* Returns a random number between min (inclusive) and max (exclusive)
* @param min
* @param max
*/
export function getRandomArbitrary(min: number, max: number) {
return Math.random() * (max - min) + min;
@@ -254,6 +346,8 @@ export function getRandomArbitrary(min: number, max: number) {
* if min isn't an integer) and no greater than max (or the next integer
* lower than max if max isn't an integer).
* Using Math.round() will give you a non-uniform distribution!
* @param min
* @param max
*/
export function getRandomInt(min: number, max: number) {
min = Math.ceil(min);
@@ -265,13 +359,13 @@ export function getRandomInt(min: number, max: number) {
* Returns either the NodeJS crypto.randomBytes() function or its
* browser equivalent implemented via window.crypto.getRandomValues()
*/
let getRandomBytes = (
(typeof window !== 'undefined' && window.crypto)
const getRandomBytes = (
(typeof window !== "undefined" && window.crypto)
// Browsers
? function () {
return (numBytes: number) => {
let randomBytes = new Uint8Array(numBytes);
const randomBytes = new Uint8Array(numBytes);
for (let i = 0; i < numBytes; i += 65536) {
window.crypto.getRandomValues(randomBytes.subarray(i, i + Math.min(numBytes - i, 65536)));
}
@@ -279,8 +373,9 @@ let getRandomBytes = (
};
}
// Node
: function() {
// Node
: function () {
// eslint-disable-next-line @typescript-eslint/no-var-requires
return require("crypto").randomBytes;
}
)();
@@ -296,35 +391,38 @@ export function getCryptoRandomInt(min: number, max: number):number {
// synchronous version of: https://github.com/joepie91/node-random-number-csprng
const range = max - min
if (range >= Math.pow(2, 32))
console.log("Warning! Range is too large.")
let tmpRange = range
let bitsNeeded = 0
let bytesNeeded = 0
let mask = 1
while (tmpRange > 0) {
if (bitsNeeded % 8 === 0) bytesNeeded += 1
bitsNeeded += 1
mask = mask << 1 | 1
tmpRange = tmpRange >>> 1
const range = max - min;
if (range >= Math.pow(2, 32)) {
console.log("Warning! Range is too large.");
}
const randomBytes = getRandomBytes(bytesNeeded)
let randomValue = 0
let tmpRange = range;
let bitsNeeded = 0;
let bytesNeeded = 0;
let mask = 1;
while (tmpRange > 0) {
if (bitsNeeded % 8 === 0) {
bytesNeeded += 1;
}
bitsNeeded += 1;
mask = mask << 1 | 1;
tmpRange = tmpRange >>> 1;
}
const randomBytes = getRandomBytes(bytesNeeded);
let randomValue = 0;
for (let i = 0; i < bytesNeeded; i++) {
randomValue |= randomBytes[i] << 8 * i
randomValue |= randomBytes[i] << 8 * i;
}
randomValue = randomValue & mask;
if (randomValue <= range) {
return min + randomValue
return min + randomValue;
} else {
return getCryptoRandomInt(min, max)
return getCryptoRandomInt(min, max);
}
}
@@ -374,17 +472,17 @@ export function parseTimeObject(time: string) {
};
}
let array = time.split(":");
const array = time.split(":");
if (array.length < 2) {
throw new Error("parseVueDatePickerTimeFormat: Invalid Time");
}
let obj = {
const obj = {
hours: parseInt(array[0]),
minutes: parseInt(array[1]),
seconds: 0,
}
};
if (array.length >= 3) {
obj.seconds = parseInt(array[2]);
}
@@ -392,6 +490,7 @@ export function parseTimeObject(time: string) {
}
/**
* @param obj
* @returns string e.g. 12:00
*/
export function parseTimeFromTimeObject(obj : any) {
@@ -401,10 +500,10 @@ export function parseTimeFromTimeObject(obj : any) {
let result = "";
result += obj.hours.toString().padStart(2, "0") + ":" + obj.minutes.toString().padStart(2, "0")
result += obj.hours.toString().padStart(2, "0") + ":" + obj.minutes.toString().padStart(2, "0");
if (obj.seconds) {
result += ":" + obj.seconds.toString().padStart(2, "0")
result += ":" + obj.seconds.toString().padStart(2, "0");
}
return result;
@@ -428,8 +527,11 @@ export function utcToISODateTime(input : string) {
/**
* For SQL_DATETIME_FORMAT
* @param input
* @param format
* @returns A string date of SQL_DATETIME_FORMAT
*/
export function utcToLocal(input : string, format = SQL_DATETIME_FORMAT) {
export function utcToLocal(input : string, format = SQL_DATETIME_FORMAT) : string {
return dayjs.utc(input).local().format(format);
}
@@ -442,3 +544,19 @@ export function utcToLocal(input : string, format = SQL_DATETIME_FORMAT) {
export function localToUTC(input : string, format = SQL_DATETIME_FORMAT) {
return dayjs(input).utc().format(format);
}
/**
* Generate a decimal integer number from a string
* @param str Input
* @param length Default is 10 which means 0 - 9
*/
export function intHash(str : string, length = 10) : number {
// A simple hashing function (you can use more complex hash functions if needed)
let hash = 0;
for (let i = 0; i < str.length; i++) {
hash += str.charCodeAt(i);
}
// Normalize the hash to the range [0, 10]
return (hash % length + length) % length; // Ensure the result is non-negative
}