64 changed files with 10492 additions and 1034 deletions
@ -0,0 +1,7 @@ |
|||
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db. |
|||
BEGIN TRANSACTION; |
|||
|
|||
ALTER TABLE monitor |
|||
ADD retry_interval INTEGER default 0 not null; |
|||
|
|||
COMMIT; |
@ -0,0 +1,19 @@ |
|||
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db. |
|||
CREATE TABLE tag ( |
|||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, |
|||
name VARCHAR(255) NOT NULL, |
|||
color VARCHAR(255) NOT NULL, |
|||
created_date DATETIME DEFAULT (DATETIME('now')) NOT NULL |
|||
); |
|||
|
|||
CREATE TABLE monitor_tag ( |
|||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, |
|||
monitor_id INTEGER NOT NULL, |
|||
tag_id INTEGER NOT NULL, |
|||
value TEXT, |
|||
CONSTRAINT FK_tag FOREIGN KEY (tag_id) REFERENCES tag(id) ON DELETE CASCADE ON UPDATE CASCADE, |
|||
CONSTRAINT FK_monitor FOREIGN KEY (monitor_id) REFERENCES monitor(id) ON DELETE CASCADE ON UPDATE CASCADE |
|||
); |
|||
|
|||
CREATE INDEX monitor_tag_monitor_id_index ON monitor_tag (monitor_id); |
|||
CREATE INDEX monitor_tag_tag_id_index ON monitor_tag (tag_id); |
@ -0,0 +1,21 @@ |
|||
#!/usr/bin/env sh |
|||
|
|||
# set -e Exit the script if an error happens |
|||
set -e |
|||
PUID=${PUID=1000} |
|||
PGID=${PGID=1000} |
|||
|
|||
files_ownership () { |
|||
# -h Changes the ownership of an encountered symbolic link and not that of the file or directory pointed to by the symbolic link. |
|||
# -R Recursively descends the specified directories |
|||
# -c Like verbose but report only when a change is made |
|||
chown -hRc "$PUID":"$PGID" /app/data |
|||
} |
|||
|
|||
echo "==> Performing startup jobs and maintenance tasks" |
|||
files_ownership |
|||
|
|||
echo "==> Starting application with user $PUID group $PGID" |
|||
|
|||
# --clear-groups Clear supplementary groups. |
|||
exec setpriv --reuid "$PUID" --regid "$PGID" --clear-groups "$@" |
File diff suppressed because it is too large
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 9.5 KiB |
@ -0,0 +1,19 @@ |
|||
{ |
|||
"name": "Uptime Kuma", |
|||
"short_name": "Uptime Kuma", |
|||
"start_url": "/", |
|||
"background_color": "#fff", |
|||
"display": "standalone", |
|||
"icons": [ |
|||
{ |
|||
"src": "icon-192x192.png", |
|||
"sizes": "192x192", |
|||
"type": "image/png" |
|||
}, |
|||
{ |
|||
"src": "icon-512x512.png", |
|||
"sizes": "512x512", |
|||
"type": "image/png" |
|||
} |
|||
] |
|||
} |
@ -0,0 +1,13 @@ |
|||
const { BeanModel } = require("redbean-node/dist/bean-model"); |
|||
|
|||
class Tag extends BeanModel { |
|||
toJSON() { |
|||
return { |
|||
id: this._id, |
|||
name: this._name, |
|||
color: this._color, |
|||
}; |
|||
} |
|||
} |
|||
|
|||
module.exports = Tag; |
@ -0,0 +1,124 @@ |
|||
const NotificationProvider = require("./notification-provider"); |
|||
const axios = require("axios"); |
|||
const { DOWN, UP } = require("../../src/util"); |
|||
|
|||
class Teams extends NotificationProvider { |
|||
name = "teams"; |
|||
|
|||
_statusMessageFactory = (status, monitorName) => { |
|||
if (status === DOWN) { |
|||
return `🔴 Application [${monitorName}] went down`; |
|||
} else if (status === UP) { |
|||
return `✅ Application [${monitorName}] is back online`; |
|||
} |
|||
return "Notification"; |
|||
}; |
|||
|
|||
_getThemeColor = (status) => { |
|||
if (status === DOWN) { |
|||
return "ff0000"; |
|||
} |
|||
if (status === UP) { |
|||
return "00e804"; |
|||
} |
|||
return "008cff"; |
|||
}; |
|||
|
|||
_notificationPayloadFactory = ({ |
|||
status, |
|||
monitorMessage, |
|||
monitorName, |
|||
monitorUrl, |
|||
}) => { |
|||
const notificationMessage = this._statusMessageFactory( |
|||
status, |
|||
monitorName |
|||
); |
|||
|
|||
const facts = []; |
|||
|
|||
if (monitorName) { |
|||
facts.push({ |
|||
name: "Monitor", |
|||
value: monitorName, |
|||
}); |
|||
} |
|||
|
|||
if (monitorUrl) { |
|||
facts.push({ |
|||
name: "URL", |
|||
value: monitorUrl, |
|||
}); |
|||
} |
|||
|
|||
return { |
|||
"@context": "https://schema.org/extensions", |
|||
"@type": "MessageCard", |
|||
themeColor: this._getThemeColor(status), |
|||
summary: notificationMessage, |
|||
sections: [ |
|||
{ |
|||
activityImage: |
|||
"https://raw.githubusercontent.com/louislam/uptime-kuma/master/public/icon.png", |
|||
activityTitle: "**Uptime Kuma**", |
|||
}, |
|||
{ |
|||
activityTitle: notificationMessage, |
|||
}, |
|||
{ |
|||
activityTitle: "**Description**", |
|||
text: monitorMessage, |
|||
facts, |
|||
}, |
|||
], |
|||
}; |
|||
}; |
|||
|
|||
_sendNotification = async (webhookUrl, payload) => { |
|||
await axios.post(webhookUrl, payload); |
|||
}; |
|||
|
|||
_handleGeneralNotification = (webhookUrl, msg) => { |
|||
const payload = this._notificationPayloadFactory({ |
|||
monitorMessage: msg |
|||
}); |
|||
|
|||
return this._sendNotification(webhookUrl, payload); |
|||
}; |
|||
|
|||
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { |
|||
let okMsg = "Sent Successfully. "; |
|||
|
|||
try { |
|||
if (heartbeatJSON == null) { |
|||
await this._handleGeneralNotification(notification.webhookUrl, msg); |
|||
return okMsg; |
|||
} |
|||
|
|||
let url; |
|||
|
|||
if (monitorJSON["type"] === "port") { |
|||
url = monitorJSON["hostname"]; |
|||
if (monitorJSON["port"]) { |
|||
url += ":" + monitorJSON["port"]; |
|||
} |
|||
} else { |
|||
url = monitorJSON["url"]; |
|||
} |
|||
|
|||
const payload = this._notificationPayloadFactory({ |
|||
monitorMessage: heartbeatJSON.msg, |
|||
monitorName: monitorJSON.name, |
|||
monitorUrl: url, |
|||
status: heartbeatJSON.status, |
|||
}); |
|||
|
|||
await this._sendNotification(notification.webhookUrl, payload); |
|||
return okMsg; |
|||
} catch (error) { |
|||
this.throwGeneralAxiosError(error); |
|||
} |
|||
} |
|||
} |
|||
|
|||
module.exports = Teams; |
@ -0,0 +1,73 @@ |
|||
<template> |
|||
<div class="tag-wrapper rounded d-inline-flex" |
|||
:class="{ 'px-3': size == 'normal', |
|||
'py-1': size == 'normal', |
|||
'm-2': size == 'normal', |
|||
'px-2': size == 'sm', |
|||
'py-0': size == 'sm', |
|||
'm-1': size == 'sm', |
|||
}" |
|||
:style="{ backgroundColor: item.color, fontSize: size == 'sm' ? '0.7em' : '1em' }" |
|||
> |
|||
<span class="tag-text">{{ displayText }}</span> |
|||
<span v-if="remove != null" class="ps-1 btn-remove" @click="remove(item)"> |
|||
<font-awesome-icon icon="times" /> |
|||
</span> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
props: { |
|||
item: { |
|||
type: Object, |
|||
required: true, |
|||
}, |
|||
remove: { |
|||
type: Function, |
|||
default: null, |
|||
}, |
|||
size: { |
|||
type: String, |
|||
default: "normal", |
|||
} |
|||
}, |
|||
computed: { |
|||
displayText() { |
|||
if (this.item.value == "") { |
|||
return this.item.name; |
|||
} else { |
|||
return `${this.item.name}: ${this.item.value}`; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
.tag-wrapper { |
|||
color: white; |
|||
opacity: 0.85; |
|||
|
|||
.dark & { |
|||
opacity: 1; |
|||
} |
|||
} |
|||
|
|||
.tag-text { |
|||
padding-bottom: 1px !important; |
|||
text-overflow: ellipsis; |
|||
overflow: hidden; |
|||
white-space: nowrap; |
|||
} |
|||
|
|||
.btn-remove { |
|||
font-size: 0.9em; |
|||
line-height: 24px; |
|||
opacity: 0.3; |
|||
} |
|||
|
|||
.btn-remove:hover { |
|||
opacity: 1; |
|||
} |
|||
</style> |
@ -0,0 +1,405 @@ |
|||
<template> |
|||
<div> |
|||
<h4 class="mb-3">{{ $t("Tags") }}</h4> |
|||
<div class="mb-3 p-1"> |
|||
<tag |
|||
v-for="item in selectedTags" |
|||
:key="item.id" |
|||
:item="item" |
|||
:remove="deleteTag" |
|||
/> |
|||
</div> |
|||
<div class="p-1"> |
|||
<button |
|||
type="button" |
|||
class="btn btn-outline-secondary btn-add" |
|||
:disabled="processing" |
|||
@click.stop="showAddDialog" |
|||
> |
|||
<font-awesome-icon class="me-1" icon="plus" /> {{ $t("Add") }} |
|||
</button> |
|||
</div> |
|||
<div ref="modal" class="modal fade" tabindex="-1"> |
|||
<div class="modal-dialog modal-dialog-centered"> |
|||
<div class="modal-content"> |
|||
<div class="modal-body"> |
|||
<vue-multiselect |
|||
v-model="newDraftTag.select" |
|||
class="mb-2" |
|||
:options="tagOptions" |
|||
:multiple="false" |
|||
:searchable="true" |
|||
:placeholder="$t('Add New below or Select...')" |
|||
track-by="id" |
|||
label="name" |
|||
> |
|||
<template #option="{ option }"> |
|||
<div class="mx-2 py-1 px-3 rounded d-inline-flex" |
|||
style="margin-top: -5px; margin-bottom: -5px; height: 24px;" |
|||
:style="{ color: textColor(option), backgroundColor: option.color + ' !important' }" |
|||
> |
|||
<span> |
|||
{{ option.name }}</span> |
|||
</div> |
|||
</template> |
|||
<template #singleLabel="{ option }"> |
|||
<div class="py-1 px-3 rounded d-inline-flex" |
|||
style="height: 24px;" |
|||
:style="{ color: textColor(option), backgroundColor: option.color + ' !important' }" |
|||
> |
|||
<span>{{ option.name }}</span> |
|||
</div> |
|||
</template> |
|||
</vue-multiselect> |
|||
<div v-if="newDraftTag.select?.name == null" class="d-flex mb-2"> |
|||
<div class="w-50 pe-2"> |
|||
<input v-model="newDraftTag.name" class="form-control" |
|||
:class="{'is-invalid': validateDraftTag.nameInvalid}" |
|||
:placeholder="$t('Name')" |
|||
@keydown.enter.prevent="onEnter" |
|||
/> |
|||
<div class="invalid-feedback"> |
|||
{{ $t("Tag with this name already exist.") }} |
|||
</div> |
|||
</div> |
|||
<div class="w-50 ps-2"> |
|||
<vue-multiselect |
|||
v-model="newDraftTag.color" |
|||
:options="colorOptions" |
|||
:multiple="false" |
|||
:searchable="true" |
|||
:placeholder="$t('color')" |
|||
track-by="color" |
|||
label="name" |
|||
select-label="" |
|||
deselect-label="" |
|||
> |
|||
<template #option="{ option }"> |
|||
<div class="mx-2 py-1 px-3 rounded d-inline-flex" |
|||
style="height: 24px; color: white;" |
|||
:style="{ backgroundColor: option.color + ' !important' }" |
|||
> |
|||
<span>{{ option.name }}</span> |
|||
</div> |
|||
</template> |
|||
<template #singleLabel="{ option }"> |
|||
<div class="py-1 px-3 rounded d-inline-flex" |
|||
style="height: 24px; color: white;" |
|||
:style="{ backgroundColor: option.color + ' !important' }" |
|||
> |
|||
<span>{{ option.name }}</span> |
|||
</div> |
|||
</template> |
|||
</vue-multiselect> |
|||
</div> |
|||
</div> |
|||
<div class="mb-2"> |
|||
<input v-model="newDraftTag.value" class="form-control" |
|||
:class="{'is-invalid': validateDraftTag.valueInvalid}" |
|||
:placeholder="$t('value (optional)')" |
|||
@keydown.enter.prevent="onEnter" |
|||
/> |
|||
<div class="invalid-feedback"> |
|||
{{ $t("Tag with this value already exist.") }} |
|||
</div> |
|||
</div> |
|||
<div class="mb-2"> |
|||
<button |
|||
type="button" |
|||
class="btn btn-secondary float-end" |
|||
:disabled="processing || validateDraftTag.invalid" |
|||
@click.stop="addDraftTag" |
|||
> |
|||
{{ $t("Add") }} |
|||
</button> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import { Modal } from "bootstrap"; |
|||
import VueMultiselect from "vue-multiselect"; |
|||
import Tag from "../components/Tag.vue"; |
|||
import { useToast } from "vue-toastification" |
|||
const toast = useToast() |
|||
|
|||
export default { |
|||
components: { |
|||
Tag, |
|||
VueMultiselect, |
|||
}, |
|||
props: { |
|||
preSelectedTags: { |
|||
type: Array, |
|||
default: () => [], |
|||
}, |
|||
}, |
|||
data() { |
|||
return { |
|||
modal: null, |
|||
existingTags: [], |
|||
processing: false, |
|||
newTags: [], |
|||
deleteTags: [], |
|||
newDraftTag: { |
|||
name: null, |
|||
select: null, |
|||
color: null, |
|||
value: "", |
|||
invalid: true, |
|||
nameInvalid: false, |
|||
}, |
|||
}; |
|||
}, |
|||
computed: { |
|||
tagOptions() { |
|||
const tagOptions = this.existingTags; |
|||
for (const tag of this.newTags) { |
|||
if (!tagOptions.find(t => t.name == tag.name && t.color == tag.color)) { |
|||
tagOptions.push(tag); |
|||
} |
|||
} |
|||
return tagOptions; |
|||
}, |
|||
selectedTags() { |
|||
return this.preSelectedTags.concat(this.newTags).filter(tag => !this.deleteTags.find(monitorTag => monitorTag.id == tag.id)); |
|||
}, |
|||
colorOptions() { |
|||
return [ |
|||
{ name: this.$t("Gray"), |
|||
color: "#4B5563" }, |
|||
{ name: this.$t("Red"), |
|||
color: "#DC2626" }, |
|||
{ name: this.$t("Orange"), |
|||
color: "#D97706" }, |
|||
{ name: this.$t("Green"), |
|||
color: "#059669" }, |
|||
{ name: this.$t("Blue"), |
|||
color: "#2563EB" }, |
|||
{ name: this.$t("Indigo"), |
|||
color: "#4F46E5" }, |
|||
{ name: this.$t("Purple"), |
|||
color: "#7C3AED" }, |
|||
{ name: this.$t("Pink"), |
|||
color: "#DB2777" }, |
|||
] |
|||
}, |
|||
validateDraftTag() { |
|||
let nameInvalid = false; |
|||
let valueInvalid = false; |
|||
let invalid = true; |
|||
if (this.deleteTags.find(tag => tag.name == this.newDraftTag.select?.name && tag.value == this.newDraftTag.value)) { |
|||
// Undo removing a Tag |
|||
nameInvalid = false; |
|||
valueInvalid = false; |
|||
invalid = false; |
|||
} else if (this.existingTags.filter(tag => tag.name === this.newDraftTag.name).length > 0) { |
|||
// Try to create new tag with existing name |
|||
nameInvalid = true; |
|||
invalid = true; |
|||
} else if (this.newTags.concat(this.preSelectedTags).filter(tag => ( |
|||
tag.name == this.newDraftTag.select?.name && tag.value == this.newDraftTag.value |
|||
) || ( |
|||
tag.name == this.newDraftTag.name && tag.value == this.newDraftTag.value |
|||
)).length > 0) { |
|||
// Try to add a tag with existing name and value |
|||
valueInvalid = true; |
|||
invalid = true; |
|||
} else if (this.newDraftTag.select != null) { |
|||
// Select an existing tag, no need to validate |
|||
invalid = false; |
|||
valueInvalid = false; |
|||
} else if (this.newDraftTag.color == null || this.newDraftTag.name === "") { |
|||
// Missing form inputs |
|||
nameInvalid = false; |
|||
invalid = true; |
|||
} else { |
|||
// Looks valid |
|||
invalid = false; |
|||
nameInvalid = false; |
|||
valueInvalid = false; |
|||
} |
|||
return { |
|||
invalid, |
|||
nameInvalid, |
|||
valueInvalid, |
|||
} |
|||
}, |
|||
}, |
|||
mounted() { |
|||
this.modal = new Modal(this.$refs.modal); |
|||
this.getExistingTags(); |
|||
}, |
|||
methods: { |
|||
showAddDialog() { |
|||
this.modal.show(); |
|||
}, |
|||
getExistingTags() { |
|||
this.$root.getSocket().emit("getTags", (res) => { |
|||
if (res.ok) { |
|||
this.existingTags = res.tags; |
|||
} else { |
|||
toast.error(res.msg) |
|||
} |
|||
}); |
|||
}, |
|||
deleteTag(item) { |
|||
if (item.new) { |
|||
// Undo Adding a new Tag |
|||
this.newTags = this.newTags.filter(tag => !(tag.name == item.name && tag.value == item.value)); |
|||
} else { |
|||
// Remove an Existing Tag |
|||
this.deleteTags.push(item); |
|||
} |
|||
}, |
|||
textColor(option) { |
|||
if (option.color) { |
|||
return "white"; |
|||
} else { |
|||
return this.$root.theme === "light" ? "var(--bs-body-color)" : "inherit"; |
|||
} |
|||
}, |
|||
addDraftTag() { |
|||
console.log("Adding Draft Tag: ", this.newDraftTag); |
|||
if (this.newDraftTag.select != null) { |
|||
if (this.deleteTags.find(tag => tag.name == this.newDraftTag.select.name && tag.value == this.newDraftTag.value)) { |
|||
// Undo removing a tag |
|||
this.deleteTags = this.deleteTags.filter(tag => !(tag.name == this.newDraftTag.select.name && tag.value == this.newDraftTag.value)); |
|||
} else { |
|||
// Add an existing Tag |
|||
this.newTags.push({ |
|||
id: this.newDraftTag.select.id, |
|||
color: this.newDraftTag.select.color, |
|||
name: this.newDraftTag.select.name, |
|||
value: this.newDraftTag.value, |
|||
new: true, |
|||
}) |
|||
} |
|||
} else { |
|||
// Add new Tag |
|||
this.newTags.push({ |
|||
color: this.newDraftTag.color.color, |
|||
name: this.newDraftTag.name.trim(), |
|||
value: this.newDraftTag.value, |
|||
new: true, |
|||
}) |
|||
} |
|||
this.clearDraftTag(); |
|||
}, |
|||
clearDraftTag() { |
|||
this.newDraftTag = { |
|||
name: null, |
|||
select: null, |
|||
color: null, |
|||
value: "", |
|||
invalid: true, |
|||
nameInvalid: false, |
|||
}; |
|||
this.modal.hide(); |
|||
}, |
|||
addTagAsync(newTag) { |
|||
return new Promise((resolve) => { |
|||
this.$root.getSocket().emit("addTag", newTag, resolve); |
|||
}); |
|||
}, |
|||
addMonitorTagAsync(tagId, monitorId, value) { |
|||
return new Promise((resolve) => { |
|||
this.$root.getSocket().emit("addMonitorTag", tagId, monitorId, value, resolve); |
|||
}); |
|||
}, |
|||
deleteMonitorTagAsync(tagId, monitorId, value) { |
|||
return new Promise((resolve) => { |
|||
this.$root.getSocket().emit("deleteMonitorTag", tagId, monitorId, value, resolve); |
|||
}); |
|||
}, |
|||
onEnter() { |
|||
if (!this.validateDraftTag.invalid) { |
|||
this.addDraftTag(); |
|||
} |
|||
}, |
|||
async submit(monitorId) { |
|||
console.log(`Submitting tag changes for monitor ${monitorId}...`); |
|||
this.processing = true; |
|||
|
|||
for (const newTag of this.newTags) { |
|||
let tagId; |
|||
if (newTag.id == null) { |
|||
// Create a New Tag |
|||
let newTagResult; |
|||
await this.addTagAsync(newTag).then((res) => { |
|||
if (!res.ok) { |
|||
toast.error(res.msg); |
|||
newTagResult = false; |
|||
} |
|||
newTagResult = res.tag; |
|||
}); |
|||
if (!newTagResult) { |
|||
// abort |
|||
this.processing = false; |
|||
return; |
|||
} |
|||
tagId = newTagResult.id; |
|||
// Assign the new ID to the tags of the same name & color |
|||
this.newTags.map(tag => { |
|||
if (tag.name == newTag.name && tag.color == newTag.color) { |
|||
tag.id = newTagResult.id; |
|||
} |
|||
}) |
|||
} else { |
|||
tagId = newTag.id; |
|||
} |
|||
|
|||
let newMonitorTagResult; |
|||
// Assign tag to monitor |
|||
await this.addMonitorTagAsync(tagId, monitorId, newTag.value).then((res) => { |
|||
if (!res.ok) { |
|||
toast.error(res.msg); |
|||
newMonitorTagResult = false; |
|||
} |
|||
newMonitorTagResult = true; |
|||
}); |
|||
if (!newMonitorTagResult) { |
|||
// abort |
|||
this.processing = false; |
|||
return; |
|||
} |
|||
} |
|||
|
|||
for (const deleteTag of this.deleteTags) { |
|||
let deleteMonitorTagResult; |
|||
await this.deleteMonitorTagAsync(deleteTag.tag_id, deleteTag.monitor_id, deleteTag.value).then((res) => { |
|||
if (!res.ok) { |
|||
toast.error(res.msg); |
|||
deleteMonitorTagResult = false; |
|||
} |
|||
deleteMonitorTagResult = true; |
|||
}); |
|||
if (!deleteMonitorTagResult) { |
|||
// abort |
|||
this.processing = false; |
|||
return; |
|||
} |
|||
} |
|||
|
|||
this.getExistingTags(); |
|||
this.newTags = []; |
|||
this.deleteTags = []; |
|||
this.processing = false; |
|||
} |
|||
}, |
|||
}; |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.btn-add { |
|||
width: 100%; |
|||
} |
|||
|
|||
.modal-body { |
|||
padding: 1.5rem; |
|||
} |
|||
</style> |
@ -0,0 +1,29 @@ |
|||
<template> |
|||
<div class="mb-3"> |
|||
<label for="teams-webhookurl" class="form-label">Webhook URL</label> |
|||
<input |
|||
id="teams-webhookurl" |
|||
v-model="$parent.notification.webhookUrl" |
|||
type="text" |
|||
class="form-control" |
|||
required |
|||
/> |
|||
<div class="form-text"> |
|||
You can learn how to create a webhook url |
|||
<a |
|||
href="https://docs.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/add-incoming-webhook" |
|||
target="_blank" |
|||
>here</a>. |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
data() { |
|||
return { |
|||
name: "teams", |
|||
}; |
|||
}, |
|||
}; |
|||
</script> |
@ -0,0 +1,48 @@ |
|||
import { createI18n } from "vue-i18n"; |
|||
import daDK from "./languages/da-DK"; |
|||
import deDE from "./languages/de-DE"; |
|||
import en from "./languages/en"; |
|||
import esEs from "./languages/es-ES"; |
|||
import etEE from "./languages/et-EE"; |
|||
import frFR from "./languages/fr-FR"; |
|||
import itIT from "./languages/it-IT"; |
|||
import ja from "./languages/ja"; |
|||
import koKR from "./languages/ko-KR"; |
|||
import nlNL from "./languages/nl-NL"; |
|||
import pl from "./languages/pl"; |
|||
import ruRU from "./languages/ru-RU"; |
|||
import sr from "./languages/sr"; |
|||
import srLatn from "./languages/sr-latn"; |
|||
import trTR from "./languages/tr-TR"; |
|||
import svSE from "./languages/sv-SE"; |
|||
import zhCN from "./languages/zh-CN"; |
|||
import zhHK from "./languages/zh-HK"; |
|||
|
|||
const languageList = { |
|||
en, |
|||
"zh-HK": zhHK, |
|||
"de-DE": deDE, |
|||
"nl-NL": nlNL, |
|||
"es-ES": esEs, |
|||
"fr-FR": frFR, |
|||
"it-IT": itIT, |
|||
"ja": ja, |
|||
"da-DK": daDK, |
|||
"sr": sr, |
|||
"sr-latn": srLatn, |
|||
"sv-SE": svSE, |
|||
"tr-TR": trTR, |
|||
"ko-KR": koKR, |
|||
"ru-RU": ruRU, |
|||
"zh-CN": zhCN, |
|||
"pl": pl, |
|||
"et-EE": etEE, |
|||
}; |
|||
|
|||
export const i18n = createI18n({ |
|||
locale: localStorage.locale || "en", |
|||
fallbackLocale: "en", |
|||
silentFallbackWarn: true, |
|||
silentTranslationWarn: false, |
|||
messages: languageList, |
|||
}); |
@ -0,0 +1,171 @@ |
|||
export default { |
|||
languageName: "Türkçe", |
|||
checkEverySecond: "{0} Saniyede bir kontrol et.", |
|||
retriesDescription: "Servisin kapalı olarak işaretlenmeden ve bir bildirim gönderilmeden önce maksimum yeniden deneme sayısı", |
|||
ignoreTLSError: "HTTPS web siteleri için TLS/SSL hatasını yoksay", |
|||
upsideDownModeDescription: "Servisin durumunu tersine çevirir. Servis çalışıyorsa kapalı olarak işaretler.", |
|||
maxRedirectDescription: "İzlenecek maksimum yönlendirme sayısı. Yönlendirmeleri devre dışı bırakmak için 0'a ayarlayın.", |
|||
acceptedStatusCodesDescription: "Servisin çalıştığını hangi durum kodları belirlesin?", |
|||
passwordNotMatchMsg: "Şifre eşleşmiyor.", |
|||
notificationDescription: "Servislerin bildirim gönderebilmesi için bir bildirim yöntemi belirleyin.", |
|||
keywordDescription: "Anahtar kelimeyi düz html veya JSON yanıtında arayın ve büyük/küçük harfe duyarlıdır", |
|||
pauseDashboardHome: "Durdur", |
|||
deleteMonitorMsg: "Servisi silmek istediğinden emin misin?", |
|||
deleteNotificationMsg: "Bu bildirimi tüm servisler için silmek istediğinden emin misin?", |
|||
resoverserverDescription: "Cloudflare varsayılan sunucudur, çözümleyici sunucusunu istediğiniz zaman değiştirebilirsiniz.", |
|||
rrtypeDescription: "İzlemek istediğiniz servisin RR-Tipini seçin", |
|||
pauseMonitorMsg: "Durdurmak istediğinden emin misin?", |
|||
clearEventsMsg: "Bu servisin bütün kayıtlarını silmek istediğinden emin misin?", |
|||
clearHeartbeatsMsg: "Bu servis için tüm sağlık durumunu silmek istediğinden emin misin?", |
|||
confirmClearStatisticsMsg: "Tüm istatistikleri silmek istediğinden emin misin?", |
|||
Settings: "Ayarlar", |
|||
Dashboard: "Panel", |
|||
"New Update": "Yeni Güncelleme", |
|||
Language: "Dil", |
|||
Appearance: "Görünüm", |
|||
Theme: "Tema", |
|||
General: "Genel", |
|||
Version: "Versiyon", |
|||
"Check Update On GitHub": "GitHub'da Güncellemeyi Kontrol Edin", |
|||
List: "Liste", |
|||
Add: "Ekle", |
|||
"Add New Monitor": "Yeni Servis Ekle", |
|||
"Quick Stats": "Servis istatistikleri", |
|||
Up: "Normal", |
|||
Down: "Hatalı", |
|||
Pending: "Bekliyor", |
|||
Unknown: "Bilinmeyen", |
|||
Pause: "Durdur", |
|||
Name: "Servis ismi", |
|||
Status: "Durum", |
|||
DateTime: "Zaman", |
|||
Message: "Mesaj", |
|||
"No important events": "Önemli olay yok", |
|||
Resume: "Devam et", |
|||
Edit: "Düzenle", |
|||
Delete: "Sil", |
|||
Current: "Şu anda", |
|||
Uptime: "Çalışma zamanı", |
|||
"Cert Exp.": "Sertifika Süresi", |
|||
days: "günler", |
|||
day: "gün", |
|||
"-day": "-gün", |
|||
hour: "saat", |
|||
"-hour": "-saat", |
|||
Response: "Cevap Süresi", |
|||
Ping: "Ping", |
|||
"Monitor Type": "Servis Tipi", |
|||
Keyword: "Anahtar Kelime", |
|||
"Friendly Name": "Panelde görünecek isim", |
|||
URL: "URL", |
|||
Hostname: "IP Adresi", |
|||
Port: "Port", |
|||
"Heartbeat Interval": "Servis Test Aralığı", |
|||
Retries: "Yeniden deneme", |
|||
Advanced: "Gelişmiş", |
|||
"Upside Down Mode": "Ters/Düz Modu", |
|||
"Max. Redirects": "Maksimum Yönlendirme", |
|||
"Accepted Status Codes": "Kabul Edilen Durum Kodları", |
|||
Save: "Kaydet", |
|||
Notifications: "Bildirimler", |
|||
"Not available, please setup.": "Atanmış bildirim yöntemi yok. Ayarlardan belirleyebilirsiniz.", |
|||
"Setup Notification": "Bildirim yöntemi kur", |
|||
Light: "Açık", |
|||
Dark: "Koyu", |
|||
Auto: "Oto", |
|||
"Theme - Heartbeat Bar": "Servis Bar Konumu", |
|||
Normal: "Normal", |
|||
Bottom: "Aşağıda", |
|||
None: "Gösterme", |
|||
Timezone: "Zaman Dilimi", |
|||
"Search Engine Visibility": "Arama Motoru Görünürlüğü", |
|||
"Allow indexing": "İndekslemeye izin ver", |
|||
"Discourage search engines from indexing site": "İndekslemeyi reddet", |
|||
"Change Password": "Şifre Değiştir", |
|||
"Current Password": "Şuan ki Şifre", |
|||
"New Password": "Yeni Şifre", |
|||
"Repeat New Password": "Yeni Şifreyi Tekrar Girin", |
|||
"Update Password": "Şifreyi Değiştir", |
|||
"Disable Auth": "Şifreli girişi iptal et.", |
|||
"Enable Auth": "Şifreli girişi aktif et.", |
|||
Logout: "Çıkış yap", |
|||
Leave: "Ayrıl", |
|||
"I understand, please disable": "Evet farkındayım, iptal et", |
|||
Confirm: "Onayla", |
|||
Yes: "Evet", |
|||
No: "Hayır", |
|||
Username: "Kullanıcı Adı", |
|||
Password: "Şifre", |
|||
"Remember me": "Beni Hatırla", |
|||
Login: "Giriş yap", |
|||
"No Monitors, please": "Servis yok, lütfen", |
|||
"add one": "bir servis ekleyin", |
|||
"Notification Type": "Bildirim Yöntemi", |
|||
Email: "E-mail", |
|||
Test: "Test", |
|||
"Certificate Info": "Sertifika Bilgisi", |
|||
"Resolver Server": "Çözümleyici Sunucu", |
|||
"Resource Record Type": "Kaynak Kayıt Türü", |
|||
"Last Result": "En son sonuçlar", |
|||
"Create your admin account": "Yönetici hesabınızı oluşturun", |
|||
"Repeat Password": "Şifrenizi tekrar girin", |
|||
respTime: "Cevap Süresi (ms)", |
|||
notAvailableShort: "N/A", |
|||
Create: "Yarat", |
|||
"Clear Data": "Verileri Temizle", |
|||
Events: "Olaylar", |
|||
Heartbeats: "Sağlık Durumları", |
|||
"Auto Get": "Otomatik Al", |
|||
retryCheckEverySecond: "Retry every {0} seconds.", |
|||
enableDefaultNotificationDescription: "For every new monitor this notification will be enabled by default. You can still disable the notification separately for each monitor.", |
|||
importHandleDescription: "Choose 'Skip existing' if you want to skip every monitor or notification with the same name. 'Overwrite' will delete every existing monitor and notification.", |
|||
confirmImportMsg: "Are you sure to import the backup? Please make sure you've selected the right import option.", |
|||
twoFAVerifyLabel: "Please type in your token to verify that 2FA is working", |
|||
tokenValidSettingsMsg: "Token is valid! You can now save the 2FA settings.", |
|||
confirmEnableTwoFAMsg: "Are you sure you want to enable 2FA?", |
|||
confirmDisableTwoFAMsg: "Are you sure you want to disable 2FA?", |
|||
"Heartbeat Retry Interval": "Heartbeat Retry Interval", |
|||
"Import Backup": "Import Backup", |
|||
"Export Backup": "Export Backup", |
|||
Export: "Export", |
|||
Import: "Import", |
|||
"Default enabled": "Default enabled", |
|||
"Apply on all existing monitors": "Apply on all existing monitors", |
|||
backupDescription: "You can backup all monitors and all notifications into a JSON file.", |
|||
backupDescription2: "PS: History and event data is not included.", |
|||
backupDescription3: "Sensitive data such as notification tokens is included in the export file, please keep it carefully.", |
|||
alertNoFile: "Please select a file to import.", |
|||
alertWrongFileType: "Please select a JSON file.", |
|||
"Clear all statistics": "Clear all Statistics", |
|||
"Skip existing": "Skip existing", |
|||
Overwrite: "Overwrite", |
|||
Options: "Options", |
|||
"Keep both": "Keep both", |
|||
"Verify Token": "Verify Token", |
|||
"Setup 2FA": "Setup 2FA", |
|||
"Enable 2FA": "Enable 2FA", |
|||
"Disable 2FA": "Disable 2FA", |
|||
"2FA Settings": "2FA Settings", |
|||
"Two Factor Authentication": "Two Factor Authentication", |
|||
Active: "Active", |
|||
Inactive: "Inactive", |
|||
Token: "Token", |
|||
"Show URI": "Show URI", |
|||
Tags: "Tags", |
|||
"Add New below or Select...": "Add New below or Select...", |
|||
"Tag with this name already exist.": "Tag with this name already exist.", |
|||
"Tag with this value already exist.": "Tag with this value already exist.", |
|||
color: "color", |
|||
"value (optional)": "value (optional)", |
|||
Gray: "Gray", |
|||
Red: "Red", |
|||
Orange: "Orange", |
|||
Green: "Green", |
|||
Blue: "Blue", |
|||
Indigo: "Indigo", |
|||
Purple: "Purple", |
|||
Pink: "Pink", |
|||
"Search...": "Search...", |
|||
"Avg. Ping": "Avg. Ping", |
|||
"Avg. Response": "Avg. Response", |
|||
} |
@ -0,0 +1,71 @@ |
|||
import { createRouter, createWebHistory } from "vue-router"; |
|||
import EmptyLayout from "./layouts/EmptyLayout.vue"; |
|||
import Layout from "./layouts/Layout.vue"; |
|||
import Dashboard from "./pages/Dashboard.vue"; |
|||
import DashboardHome from "./pages/DashboardHome.vue"; |
|||
import Details from "./pages/Details.vue"; |
|||
import EditMonitor from "./pages/EditMonitor.vue"; |
|||
import List from "./pages/List.vue"; |
|||
import Settings from "./pages/Settings.vue"; |
|||
import Setup from "./pages/Setup.vue"; |
|||
|
|||
const routes = [ |
|||
{ |
|||
path: "/", |
|||
component: Layout, |
|||
children: [ |
|||
{ |
|||
name: "root", |
|||
path: "", |
|||
component: Dashboard, |
|||
children: [ |
|||
{ |
|||
name: "DashboardHome", |
|||
path: "/dashboard", |
|||
component: DashboardHome, |
|||
children: [ |
|||
{ |
|||
path: "/dashboard/:id", |
|||
component: EmptyLayout, |
|||
children: [ |
|||
{ |
|||
path: "", |
|||
component: Details, |
|||
}, |
|||
{ |
|||
path: "/edit/:id", |
|||
component: EditMonitor, |
|||
}, |
|||
], |
|||
}, |
|||
{ |
|||
path: "/add", |
|||
component: EditMonitor, |
|||
}, |
|||
{ |
|||
path: "/list", |
|||
component: List, |
|||
}, |
|||
], |
|||
}, |
|||
{ |
|||
path: "/settings", |
|||
component: Settings, |
|||
}, |
|||
], |
|||
}, |
|||
|
|||
], |
|||
|
|||
}, |
|||
{ |
|||
path: "/setup", |
|||
component: Setup, |
|||
}, |
|||
] |
|||
|
|||
export const router = createRouter({ |
|||
linkActiveClass: "active", |
|||
history: createWebHistory(), |
|||
routes, |
|||
}); |
@ -0,0 +1,10 @@ |
|||
FROM ubuntu |
|||
WORKDIR /app |
|||
RUN apt update && apt --yes install git curl |
|||
RUN curl -sL https://deb.nodesource.com/setup_16.x | bash - |
|||
RUN apt --yes install nodejs |
|||
RUN git clone https://github.com/louislam/uptime-kuma.git . |
|||
RUN npm run setup |
|||
|
|||
# Option 1. Try it |
|||
RUN node server/server.js |
Loading…
Reference in new issue