Merge branch 'master' into default-notification

This commit is contained in:
Ponkhy 2021-09-07 14:07:50 +02:00 committed by GitHub
commit 4faa409027
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 599 additions and 179 deletions

View File

@ -27,6 +27,7 @@ VPS is sponsored by Uptime Kuma sponsors on [Open Collective](https://opencollec
* Fancy, Reactive, Fast UI/UX.
* Notifications via Telegram, Discord, Gotify, Slack, Pushover, Email (SMTP), and [70+ notification services, click here for the full list](https://github.com/louislam/uptime-kuma/issues/284).
* 20 seconds interval.
* [Multi Languages](https://github.com/louislam/uptime-kuma/tree/master/src/languages)
## 🔧 How to Install

View File

@ -1,25 +1,30 @@
# DON'T UPDATE TO alpine3.13, 1.14, see #41.
FROM node:14-alpine3.12 AS release
FROM node:14-bullseye-slim AS release
WORKDIR /app
# install dependencies
RUN apt update && apt --yes install python3 python3-pip python3-dev git g++ make iputils-ping
RUN ln -s /usr/bin/python3 /usr/bin/python
# split the sqlite install here, so that it can caches the arm prebuilt
RUN apk add --no-cache --virtual .build-deps make g++ python3 python3-dev git && \
ln -s /usr/bin/python3 /usr/bin/python && \
npm install mapbox/node-sqlite3#593c9d && \
apk del .build-deps && \
rm -f /usr/bin/python
RUN npm install mapbox/node-sqlite3#593c9d
# Install apprise
RUN apk add --no-cache python3 py3-cryptography py3-pip py3-six py3-yaml py3-click py3-markdown py3-requests py3-requests-oauthlib
RUN apt --yes install python3-cryptography python3-six python3-yaml python3-click python3-markdown python3-requests python3-requests-oauthlib
RUN pip3 --no-cache-dir install apprise && \
rm -rf /root/.cache
# additional package should be added here, since we don't want to re-compile the arm prebuilt again
# add sqlite3 cli for debugging in the future
RUN apt --yes install sqlite3
COPY . .
RUN npm install --legacy-peer-deps && npm run build && npm prune
EXPOSE 3001
VOLUME ["/app/data"]
HEALTHCHECK --interval=60s --timeout=30s --start-period=300s CMD node extra/healthcheck.js
HEALTHCHECK --interval=600s --timeout=130s --start-period=300s CMD node extra/healthcheck.js
CMD ["node", "server/server.js"]
FROM release AS nightly

26
dockerfile-alpine Normal file
View File

@ -0,0 +1,26 @@
# DON'T UPDATE TO alpine3.13, 1.14, see #41.
FROM node:14-alpine3.12 AS release
WORKDIR /app
# split the sqlite install here, so that it can caches the arm prebuilt
RUN apk add --no-cache --virtual .build-deps make g++ python3 python3-dev git && \
ln -s /usr/bin/python3 /usr/bin/python && \
npm install mapbox/node-sqlite3#593c9d && \
apk del .build-deps && \
rm -f /usr/bin/python
# Install apprise
RUN apk add --no-cache python3 py3-cryptography py3-pip py3-six py3-yaml py3-click py3-markdown py3-requests py3-requests-oauthlib
RUN pip3 --no-cache-dir install apprise && \
rm -rf /root/.cache
COPY . .
RUN npm install --legacy-peer-deps && npm run build && npm prune
EXPOSE 3001
VOLUME ["/app/data"]
HEALTHCHECK --interval=600s --timeout=130s --start-period=300s CMD node extra/healthcheck.js
CMD ["node", "server/server.js"]
FROM release AS nightly
RUN npm run mark-as-nightly

View File

@ -1,28 +0,0 @@
# DON'T UPDATE TO alpine3.13, 1.14, see #41.
FROM node:14-bullseye AS release
WORKDIR /app
RUN apt update
RUN apt --yes install python3 python3-pip python3-dev git g++ make
RUN ln -s /usr/bin/python3 /usr/bin/python
# split the sqlite install here, so that it can caches the arm prebuilt
RUN npm install mapbox/node-sqlite3#593c9d
# Install apprise
RUN apt --yes install python3 python3-pip python3-cryptography python3-six python3-yaml python3-click python3-markdown python3-requests python3-requests-oauthlib
RUN pip3 --no-cache-dir install apprise && \
rm -rf /root/.cache
RUN apt --yes install iputils-ping
COPY . .
RUN npm install --legacy-peer-deps && npm run build && npm prune
EXPOSE 3001
VOLUME ["/app/data"]
HEALTHCHECK --interval=60s --timeout=30s --start-period=300s CMD node extra/healthcheck.js
CMD ["node", "server/server.js"]
FROM release AS nightly
RUN npm run mark-as-nightly

View File

@ -1,19 +1,31 @@
let http = require("http");
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
let client;
if (process.env.SSL_KEY && process.env.SSL_CERT) {
client = require("https");
} else {
client = require("http");
}
let options = {
host: "localhost",
port: "3001",
timeout: 2000,
host: process.env.HOST || "127.0.0.1",
port: parseInt(process.env.PORT) || 3001,
timeout: 120 * 1000,
};
let request = http.request(options, (res) => {
console.log(`STATUS: ${res.statusCode}`);
if (res.statusCode == 200) {
let request = client.request(options, (res) => {
console.log(`Health Check OK [Res Code: ${res.statusCode}]`);
if (res.statusCode === 200) {
process.exit(0);
} else {
process.exit(1);
}
});
request.on("error", function (err) {
console.log("ERROR");
console.error("Health Check ERROR");
process.exit(1);
});
request.end();

View File

@ -19,8 +19,8 @@
"build": "vite build",
"vite-preview-dist": "vite preview --host",
"build-docker": "npm run build-docker-alpine && npm run build-docker-debian",
"build-docker-alpine": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma -t louislam/uptime-kuma:1 -t louislam/uptime-kuma:1.5.3 -t louislam/uptime-kuma:alpine -t louislam/uptime-kuma:1-alpine -t louislam/uptime-kuma:1.5.3-alpine --target release . --push",
"build-docker-debian": "docker buildx build -f dockerfile-debian --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:debian -t louislam/uptime-kuma:1-debian -t louislam/uptime-kuma:1.5.3-debian --target release . --push",
"build-docker-alpine": "docker buildx build -f dockerfile-alpine --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:alpine -t louislam/uptime-kuma:1-alpine -t louislam/uptime-kuma:1.5.3-alpine --target release . --push",
"build-docker-debian": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma -t louislam/uptime-kuma:1 -t louislam/uptime-kuma:1.5.3 -t louislam/uptime-kuma:debian -t louislam/uptime-kuma:1-debian -t louislam/uptime-kuma:1.5.3-debian --target release . --push",
"build-docker-nightly": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:nightly --target nightly . --push",
"build-docker-nightly-amd64": "docker buildx build --platform linux/amd64 -t louislam/uptime-kuma:nightly-amd64 --target nightly . --push --progress plain",
"setup": "git checkout 1.5.3 && npm install --legacy-peer-deps && node node_modules/esbuild/install.js && npm run build && npm prune",

91
server/client.js Normal file
View File

@ -0,0 +1,91 @@
/*
* For Client Socket
*/
const { TimeLogger } = require("../src/util");
const { R } = require("redbean-node");
const { io } = require("./server");
async function sendNotificationList(socket) {
const timeLogger = new TimeLogger();
let result = [];
let list = await R.find("notification", " user_id = ? ", [
socket.userID,
]);
for (let bean of list) {
result.push(bean.export())
}
io.to(socket.userID).emit("notificationList", result)
timeLogger.print("Send Notification List");
return list;
}
/**
* Send Heartbeat History list to socket
* @param toUser True = send to all browsers with the same user id, False = send to the current browser only
* @param overwrite Overwrite client-side's heartbeat list
*/
async function sendHeartbeatList(socket, monitorID, toUser = false, overwrite = false) {
const timeLogger = new TimeLogger();
let list = await R.find("heartbeat", `
monitor_id = ?
ORDER BY time DESC
LIMIT 100
`, [
monitorID,
])
let result = [];
for (let bean of list) {
result.unshift(bean.toJSON());
}
if (toUser) {
io.to(socket.userID).emit("heartbeatList", monitorID, result, overwrite);
} else {
socket.emit("heartbeatList", monitorID, result, overwrite);
}
timeLogger.print(`[Monitor: ${monitorID}] sendHeartbeatList`);
}
/**
* Important Heart beat list (aka event list)
* @param socket
* @param monitorID
* @param toUser True = send to all browsers with the same user id, False = send to the current browser only
* @param overwrite Overwrite client-side's heartbeat list
*/
async function sendImportantHeartbeatList(socket, monitorID, toUser = false, overwrite = false) {
const timeLogger = new TimeLogger();
let list = await R.find("heartbeat", `
monitor_id = ?
AND important = 1
ORDER BY time DESC
LIMIT 500
`, [
monitorID,
])
timeLogger.print(`[Monitor: ${monitorID}] sendImportantHeartbeatList`);
if (toUser) {
io.to(socket.userID).emit("importantHeartbeatList", monitorID, list, overwrite);
} else {
socket.emit("importantHeartbeatList", monitorID, list, overwrite);
}
}
module.exports = {
sendNotificationList,
sendImportantHeartbeatList,
sendHeartbeatList,
}

View File

@ -82,7 +82,12 @@ if (sslKey && sslCert) {
}
const io = new Server(server);
app.use(express.json())
module.exports.io = io;
// Must be after io instantiation
const { sendNotificationList, sendHeartbeatList, sendImportantHeartbeatList } = require("./client");
app.use(express.json());
/**
* Total WebSocket client connected to server currently, no actual use
@ -585,6 +590,76 @@ let indexHTML = fs.readFileSync("./dist/index.html").toString();
}
});
socket.on("clearEvents", async (monitorID, callback) => {
try {
checkLogin(socket)
console.log(`Clear Events Monitor: ${monitorID} User ID: ${socket.userID}`)
await R.exec("UPDATE heartbeat SET msg = ?, important = ? WHERE monitor_id = ? ", [
"",
"0",
monitorID,
]);
await sendImportantHeartbeatList(socket, monitorID, true, true);
callback({
ok: true,
});
} catch (e) {
callback({
ok: false,
msg: e.message,
});
}
});
socket.on("clearHeartbeats", async (monitorID, callback) => {
try {
checkLogin(socket)
console.log(`Clear Heartbeats Monitor: ${monitorID} User ID: ${socket.userID}`)
await R.exec("DELETE FROM heartbeat WHERE monitor_id = ?", [
monitorID
]);
await sendHeartbeatList(socket, monitorID, true, true);
callback({
ok: true,
});
} catch (e) {
callback({
ok: false,
msg: e.message,
});
}
});
socket.on("clearStatistics", async (callback) => {
try {
checkLogin(socket)
console.log(`Clear Statistics User ID: ${socket.userID}`)
await R.exec("DELETE FROM heartbeat");
callback({
ok: true,
});
} catch (e) {
callback({
ok: false,
msg: e.message,
});
}
});
debug("added all socket handlers")
// ***************************
@ -653,25 +728,6 @@ async function sendMonitorList(socket) {
return list;
}
async function sendNotificationList(socket) {
const timeLogger = new TimeLogger();
let result = [];
let list = await R.find("notification", " user_id = ? ", [
socket.userID,
]);
for (let bean of list) {
result.push(bean.export())
}
io.to(socket.userID).emit("notificationList", result)
timeLogger.print("Send Notification List");
return list;
}
async function afterLogin(socket, user) {
socket.userID = user.id;
socket.join(user.id)
@ -806,48 +862,6 @@ async function startMonitors() {
}
}
/**
* Send Heartbeat History list to socket
*/
async function sendHeartbeatList(socket, monitorID) {
const timeLogger = new TimeLogger();
let list = await R.find("heartbeat", `
monitor_id = ?
ORDER BY time DESC
LIMIT 100
`, [
monitorID,
])
let result = [];
for (let bean of list) {
result.unshift(bean.toJSON())
}
socket.emit("heartbeatList", monitorID, result)
timeLogger.print(`[Monitor: ${monitorID}] sendHeartbeatList`)
}
async function sendImportantHeartbeatList(socket, monitorID) {
const timeLogger = new TimeLogger();
let list = await R.find("heartbeat", `
monitor_id = ?
AND important = 1
ORDER BY time DESC
LIMIT 500
`, [
monitorID,
])
timeLogger.print(`[Monitor: ${monitorID}] sendImportantHeartbeatList`);
socket.emit("importantHeartbeatList", monitorID, list)
}
async function shutdownFunction(signal) {
console.log("Shutdown requested");
console.log("Called signal: " + signal);

View File

@ -16,3 +16,5 @@ $dark-border-color: #1d2634;
$easing-in: cubic-bezier(0.54, 0.78, 0.55, 0.97);
$easing-out: cubic-bezier(0.25, 0.46, 0.45, 0.94);
$easing-in-out: cubic-bezier(0.79, 0.14, 0.15, 0.86);
$dropdown-border-radius: 0.5rem;

View File

@ -0,0 +1,102 @@
<template>
<div class="input-group mb-3">
<!--
Hack - Disable Chrome save password
readonly + onfocus
https://stackoverflow.com/questions/41217019/how-to-prevent-a-browser-from-storing-passwords
-->
<input
v-model="model"
:type="visibility"
class="form-control"
:placeholder="placeholder"
:maxlength="maxlength"
:autocomplete="autocomplete"
:required="required"
:readonly="isReadOnly"
@focus="removeReadOnly"
>
<a v-if="visibility == 'password'" class="btn btn-outline-primary" @click="showInput()">
<font-awesome-icon icon="eye" />
</a>
<a v-if="visibility == 'text'" class="btn btn-outline-primary" @click="hideInput()">
<font-awesome-icon icon="eye-slash" />
</a>
</div>
</template>
<script>
export default {
props: {
modelValue: {
type: String,
default: ""
},
placeholder: {
type: String,
default: ""
},
maxlength: {
type: Number,
default: 255
},
autocomplete: {
type: Boolean,
},
required: {
type: Boolean
},
readonly: {
type: Boolean,
default: false,
},
},
data() {
return {
visibility: "password",
readOnlyValue: false,
}
},
computed: {
model: {
get() {
return this.modelValue
},
set(value) {
this.$emit("update:modelValue", value)
}
},
isReadOnly() {
// Actually readonly from prop
if (this.readonly) {
return true;
}
// Hack - Disable Chrome save password
return this.readOnlyValue;
}
},
created() {
// Hack - Disable Chrome save password
if (this.autocomplete) {
this.readOnlyValue = "readonly";
}
},
methods: {
showInput() {
this.visibility = "text";
},
hideInput() {
this.visibility = "password";
},
// Hack - Disable Chrome save password
removeReadOnly() {
if (this.autocomplete) {
this.readOnlyValue = false;
}
}
}
}
</script>

View File

@ -40,7 +40,7 @@
<template v-if="notification.type === 'telegram'">
<div class="mb-3">
<label for="telegram-bot-token" class="form-label">Bot Token</label>
<input id="telegram-bot-token" v-model="notification.telegramBotToken" type="text" class="form-control" required>
<HiddenInput id="telegram-bot-token" v-model="notification.telegramBotToken" :required="true" :readonly="true"></HiddenInput>
<div class="form-text">
You can get a token from <a href="https://t.me/BotFather" target="_blank">https://t.me/BotFather</a>.
</div>
@ -130,7 +130,7 @@
<div class="mb-3">
<label for="password" class="form-label">Password</label>
<input id="password" v-model="notification.smtpPassword" type="password" class="form-control" autocomplete="false">
<HiddenInput id="password" v-model="notification.smtpPassword" :required="true" autocomplete="false"></HiddenInput>
</div>
<div class="mb-3">
@ -195,7 +195,7 @@
<template v-if="notification.type === 'gotify'">
<div class="mb-3">
<label for="gotify-application-token" class="form-label">Application Token</label>
<input id="gotify-application-token" v-model="notification.gotifyapplicationToken" type="text" class="form-control" required>
<HiddenInput id="gotify-application-token" v-model="notification.gotifyapplicationToken" :required="true"></HiddenInput>
</div>
<div class="mb-3">
<label for="gotify-server-url" class="form-label">Server URL</label>
@ -306,13 +306,13 @@
<template v-if="notification.type === 'pushy'">
<div class="mb-3">
<label for="pushy-app-token" class="form-label">API_KEY</label>
<input id="pushy-app-token" v-model="notification.pushyAPIKey" type="text" class="form-control" required>
<HiddenInput id="pushy-app-token" v-model="notification.pushyAPIKey" :required="true"></HiddenInput>
</div>
<div class="mb-3">
<label for="pushy-user-key" class="form-label">USER_TOKEN</label>
<div class="input-group mb-3">
<input id="pushy-user-key" v-model="notification.pushyToken" type="text" class="form-control" required>
<HiddenInput id="pushy-user-key" v-model="notification.pushyToken" :required="true"></HiddenInput>
</div>
</div>
<p style="margin-top: 8px;">
@ -323,7 +323,7 @@
<template v-if="notification.type === 'octopush'">
<div class="mb-3">
<label for="octopush-key" class="form-label">API KEY</label>
<input id="octopush-key" v-model="notification.octopushAPIKey" type="text" class="form-control" required>
<HiddenInput id="octopush-key" v-model="notification.octopushAPIKey" :required="true"></HiddenInput>
<label for="octopush-login" class="form-label">API LOGIN</label>
<input id="octopush-login" v-model="notification.octopushLogin" type="text" class="form-control" required>
</div>
@ -354,9 +354,9 @@
<template v-if="notification.type === 'pushover'">
<div class="mb-3">
<label for="pushover-user" class="form-label">User Key<span style="color: red;"><sup>*</sup></span></label>
<input id="pushover-user" v-model="notification.pushoveruserkey" type="text" class="form-control" required>
<HiddenInput id="pushover-user" v-model="notification.pushoveruserkey" :required="true"></HiddenInput>
<label for="pushover-app-token" class="form-label">Application Token<span style="color: red;"><sup>*</sup></span></label>
<input id="pushover-app-token" v-model="notification.pushoverapptoken" type="text" class="form-control" required>
<HiddenInput id="pushover-app-token" v-model="notification.pushoverapptoken" :required="true"></HiddenInput>
<label for="pushover-device" class="form-label">Device</label>
<input id="pushover-device" v-model="notification.pushoverdevice" type="text" class="form-control">
<label for="pushover-device" class="form-label">Message Title</label>
@ -442,7 +442,7 @@
<template v-if="notification.type === 'pushbullet'">
<div class="mb-3">
<label for="pushbullet-access-token" class="form-label">Access Token</label>
<input id="pushbullet-access-token" v-model="notification.pushbulletAccessToken" type="text" class="form-control" required>
<HiddenInput id="pushbullet-access-token" v-model="notification.pushbulletAccessToken" :required="true"></HiddenInput>
</div>
<p style="margin-top: 8px;">
@ -453,7 +453,7 @@
<template v-if="notification.type === 'line'">
<div class="mb-3">
<label for="line-channel-access-token" class="form-label">Channel access token</label>
<input id="line-channel-access-token" v-model="notification.lineChannelAccessToken" type="text" class="form-control" required>
<HiddenInput id="line-channel-access-token" v-model="notification.lineChannelAccessToken" :required="true"></HiddenInput>
</div>
<div class="form-text">
Line Developers Console - <b>Basic Settings</b>
@ -516,11 +516,13 @@ import { ucfirst } from "../util.ts"
import axios from "axios";
import { useToast } from "vue-toastification"
import Confirm from "./Confirm.vue";
import HiddenInput from "./HiddenInput.vue";
const toast = useToast()
export default {
components: {
Confirm,
HiddenInput,
},
props: {},
data() {

View File

@ -1,10 +1,10 @@
import { library } from "@fortawesome/fontawesome-svg-core"
import { faCog, faEdit, faPlus, faPause, faPlay, faTachometerAlt, faTrash, faList, faArrowAltCircleUp } from "@fortawesome/free-solid-svg-icons"
import { faCog, faEdit, faPlus, faPause, faPlay, faTachometerAlt, faTrash, faList, faArrowAltCircleUp, faEye, faEyeSlash } from "@fortawesome/free-solid-svg-icons"
//import { fa } from '@fortawesome/free-regular-svg-icons'
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome"
// Add Free Font Awesome Icons here
// https://fontawesome.com/v5.15/icons?d=gallery&p=2&s=solid&m=free
library.add(faCog, faEdit, faPlus, faPause, faPlay, faTachometerAlt, faTrash, faList, faArrowAltCircleUp);
library.add(faCog, faEdit, faPlus, faPause, faPlay, faTachometerAlt, faTrash, faList, faArrowAltCircleUp, faEye, faEyeSlash);
export { FontAwesomeIcon }

View File

@ -109,5 +109,11 @@ export default {
"Resource Record Type": "Resource Record Type",
respTime: "Resp. Time (ms)",
notAvailableShort: "N/A",
Create: "Create"
Create: "Create",
clearEventsMsg: "Are you sure want to delete all events for this monitor?",
clearHeartbeatsMsg: "Are you sure want to delete all heartbeats for this monitor?",
confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?",
"Clear Data": "Clear Data",
Events: "Events",
Heartbeats: "Heartbeats"
}

View File

@ -104,6 +104,12 @@ export default {
rrtypeDescription: "Wähle den RR-Typ aus, welchen du überwachen möchtest.",
"Last Result": "Letztes Ergebnis",
pauseMonitorMsg: "Bist du sicher das du den Monitor pausieren möchtest?",
clearEventsMsg: "Bist du sicher das du alle Ereignisse für diesen Monitor löschen möchtest?",
clearHeartbeatsMsg: "Bist du sicher das du alle Statistiken für diesen Monitor löschen möchtest?",
"Clear Data": "Lösche Daten",
Events: "Ereignisse",
Heartbeats: "Statistiken",
confirmClearStatisticsMsg: "Bist du sicher das du ALLE Statistiken löschen möchtest?",
"Create your admin account": "Erstelle dein Admin Konto",
"Repeat Password": "Wiederhole das Passwort",
"Resource Record Type": "Resource Record Type",

View File

@ -17,6 +17,9 @@ export default {
rrtypeDescription: "Select the RR-Type you want to monitor",
pauseMonitorMsg: "Are you sure want to pause?",
enableDefaultNotificationDescription: "For every new monitor this notification will be enabled by default. You can still disable the notification separately for each monitor.",
clearEventsMsg: "Are you sure want to delete all events for this monitor?",
clearHeartbeatsMsg: "Are you sure want to delete all heartbeats for this monitor?",
confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?",
Settings: "Settings",
Dashboard: "Dashboard",
"New Update": "New Update",
@ -113,4 +116,7 @@ export default {
"Default enabled": "Default enabled",
"Also apply to existing monitors": "Also apply to existing monitors",
Create: "Create",
"Clear Data": "Clear Data",
Events: "Events",
Heartbeats: "Heartbeats"
}

View File

@ -109,5 +109,11 @@ export default {
"Repeat Password": "Repetir contraseña",
respTime: "Tiempo de resp. (ms)",
notAvailableShort: "N/A",
Create: "Create"
Create: "Create",
clearEventsMsg: "Are you sure want to delete all events for this monitor?",
clearHeartbeatsMsg: "Are you sure want to delete all heartbeats for this monitor?",
confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?",
"Clear Data": "Clear Data",
Events: "Events",
Heartbeats: "Heartbeats"
}

View File

@ -66,7 +66,7 @@ export default {
"Theme - Heartbeat Bar": "Voir les services surveillés",
Normal: "Général",
Bottom: "En dessous",
None: "Non",
None: "Rien",
Timezone: "Fuseau Horaire",
"Search Engine Visibility": "Visibilité par les moteurs de recherche",
"Allow indexing": "Autoriser l'indexation par des moteurs de recherche",
@ -109,5 +109,11 @@ export default {
"Repeat Password": "Répéter le mot de passe",
respTime: "Temps de réponse (ms)",
notAvailableShort: "N/A",
Create: "Create"
Create: "Créer",
clearEventsMsg: "Are you sure want to delete all events for this monitor?",
clearHeartbeatsMsg: "Are you sure want to delete all heartbeats for this monitor?",
confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?",
"Clear Data": "Clear Data",
Events: "Events",
Heartbeats: "Heartbeats"
}

View File

@ -109,5 +109,11 @@ export default {
"Repeat Password": "Repeat Password",
respTime: "Resp. Time (ms)",
notAvailableShort: "N/A",
Create: "Create"
Create: "Create",
clearEventsMsg: "Are you sure want to delete all events for this monitor?",
clearHeartbeatsMsg: "Are you sure want to delete all heartbeats for this monitor?",
confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?",
"Clear Data": "Clear Data",
Events: "Events",
Heartbeats: "Heartbeats"
}

View File

@ -109,5 +109,11 @@ export default {
"Repeat Password": "비밀번호 재입력",
respTime: "응답 시간 (ms)",
notAvailableShort: "N/A",
Create: "Create"
Create: "Create",
clearEventsMsg: "Are you sure want to delete all events for this monitor?",
clearHeartbeatsMsg: "Are you sure want to delete all heartbeats for this monitor?",
confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?",
"Clear Data": "Clear Data",
Events: "Events",
Heartbeats: "Heartbeats"
}

View File

@ -109,5 +109,11 @@ export default {
"Repeat Password": "Herhaal wachtwoord",
respTime: "resp. tijd (ms)",
notAvailableShort: "N.v.t.",
Create: "Create"
Create: "Create",
clearEventsMsg: "Are you sure want to delete all events for this monitor?",
clearHeartbeatsMsg: "Are you sure want to delete all heartbeats for this monitor?",
confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?",
"Clear Data": "Clear Data",
Events: "Events",
Heartbeats: "Heartbeats"
}

View File

@ -1,20 +1,20 @@
export default {
languageName: "Polski",
checkEverySecond: "sprawdzaj co {0} sekund.",
"Avg.": "Średnia. ",
retriesDescription: "Maksymalna liczba ponownych prób, zanim usługa zostanie oznaczona jako offline i zostanie wysłane powiadomienie.",
ignoreTLSError: "Ignoruj błąd TLS/SSL dla stron HTTPS.",
upsideDownModeDescription: "Odwróć status do góry nogami. Jeśli usługa jest osiągalna, to jest niedostępna.",
maxRedirectDescription: "Maksymalna liczba przekierowań do naśladowania. Ustaw na 0, aby wyłączyć przekierowania.",
checkEverySecond: "Sprawdzaj co {0} sekund.",
"Avg.": "Średnia ",
retriesDescription: "Maksymalna liczba powtórzeń, zanim usługa zostanie oznaczona jako wyłączona i zostanie wysłane powiadomienie",
ignoreTLSError: "Ignoruj błąd TLS/SSL dla stron HTTPS",
upsideDownModeDescription: "Odwróć status do góry nogami. Jeśli usługa jest osiągalna, to jest oznaczona jako niedostępna.",
maxRedirectDescription: "Maksymalna liczba przekierowań do wykonania. Ustaw na 0, aby wyłączyć przekierowania.",
acceptedStatusCodesDescription: "Wybierz kody stanu, które są uważane za udaną odpowiedź.",
passwordNotMatchMsg: "Powtórzone hasło nie jest takie samo.",
notificationDescription: "Dodaj powiadomienie do monitorów, aby one działały.",
keywordDescription: "Wyszukaj słowo kluczowe w html lub JSON i wielkość liter ma znaczenie.",
pauseDashboardHome: "pauza",
passwordNotMatchMsg: "Powtórzone hasło nie pasuje.",
notificationDescription: "Proszę przypisać powiadomienie do monitora(ów), aby zadziałało.",
keywordDescription: "Wyszukiwanie słów kluczowych w zwykłym html lub odpowiedzi JSON. Wielkość liter ma znaczenie.",
pauseDashboardHome: "Pauza",
deleteMonitorMsg: "Czy na pewno chcesz usunąć ten monitor?",
deleteNotificationMsg: "Czy na pewno chcesz usunąć to powiadomienie ze wszystkich monitorów?",
resoverserverDescription: "Cloudflare jest domyślnym serwerem, możesz zmienić serwer resolver w dowolnym momencie.",
rrtypeDescription: "Wybierz typ RR-Type który chcesz monitorować.",
deleteNotificationMsg: "Czy na pewno chcesz usunąć to powiadomienie dla wszystkich monitorów?",
resoverserverDescription: "Cloudflare jest domyślnym serwerem, możesz zmienić serwer resolver w każdej chwili.",
rrtypeDescription: "Wybierz RR-Type który chcesz monitorować",
pauseMonitorMsg: "Czy na pewno chcesz wstrzymać?",
Settings: "Ustawienia",
Dashboard: "Panel",
@ -23,11 +23,11 @@ export default {
Appearance: "Wygląd",
Theme: "Motyw",
General: "Ogólne",
Version: "Versja",
Version: "Wersja",
"Check Update On GitHub": "Sprawdź aktualizację na GitHub.",
List: "Lista",
Add: "Dodaj",
"Add New Monitor": "Dodaj Nowy Monitor",
"Add New Monitor": "Dodaj nowy monitor",
"Quick Stats": "Szybkie statystyki",
Up: "Online",
Down: "Offline",
@ -38,46 +38,46 @@ export default {
Status: "Status",
DateTime: "Data i godzina",
Message: "Wiadomość",
"No important events": "Brak ważnych wydarzeń.",
"No important events": "Brak ważnych wydarzeń",
Resume: "Wznów",
Edit: "Edytuj",
Delete: "Usuń",
Current: "aktualny",
Uptime: "czas pracy",
"Cert Exp.": "Wygaśnięcie certyfikatu.",
Uptime: "Czas pracy",
"Cert Exp.": "Wygaśnięcie certyfikatu",
days: "dni",
day: "dzień",
"-day": "-dzień",
"-day": " dni",
hour: "godzina",
"-hour": "-godzina",
Response: "odpowiedź",
"-hour": " godziny",
Response: "Odpowiedź",
Ping: "Ping",
"Monitor Type": "Typ monitora",
Keyword: "słowo kluczowe",
"Friendly Name": "Przyjazne imię",
Keyword: "Słowo kluczowe",
"Friendly Name": "Przyjazna nazwa",
URL: "URL",
Hostname: "Hostname",
Hostname: "Nazwa hosta",
Port: "Port",
"Heartbeat Interval": "Interwał bicia serca",
Retries: "Prób",
Advanced: "Zaawansowane",
"Upside Down Mode": "Tryb do góry nogami",
"Max. Redirects": "Maks. Przekierowania",
"Max. Redirects": "Maks. przekierowania",
"Accepted Status Codes": "Akceptowane kody statusu",
Save: "Zapisz",
Notifications: "Powiadomienia",
"Not available, please setup.": "Niedostępne, proszę skonfigurować.",
"Setup Notification": "Powiadomienie konfiguracji",
"Setup Notification": "Konfiguracja powiadomień",
Light: "Jasny",
Dark: "Ciemny",
Auto: "Automatyczny",
"Theme - Heartbeat Bar": "Motyw - pasek bicia serca",
Normal: "Normalne",
Bottom: "Na dole",
None: "Nic",
None: "Brak",
Timezone: "Strefa czasowa",
"Search Engine Visibility": "Widoczność w wyszukiwarce",
"Allow indexing": "Zezwól na indeksowanie",
"Allow indexing": "Pozwól na indeksowanie",
"Discourage search engines from indexing site": "Zniechęcaj wyszukiwarki do indeksowania strony",
"Change Password": "Zmień hasło",
"Current Password": "Aktualne hasło",
@ -87,7 +87,7 @@ export default {
"Disable Auth": "Wyłącz autoryzację",
"Enable Auth": "Włącz autoryzację ",
Logout: "Wyloguj się",
Leave: "Opuść",
Leave: "Zostaw",
"I understand, please disable": "Rozumiem, proszę wyłączyć",
Confirm: "Potwierdź",
Yes: "Tak",
@ -95,8 +95,8 @@ export default {
Username: "Nazwa użytkownika",
Password: "Hasło",
"Remember me": "Zapamiętaj mnie",
Login: "Zaloguj sie",
"No Monitors, please": "Proszę bez monitorów",
Login: "Zaloguj się",
"No Monitors, please": "Brak monitorów, proszę",
"add one": "dodaj jeden",
"Notification Type": "Typ powiadomienia",
Email: "Email",
@ -104,10 +104,10 @@ export default {
"Certificate Info": "Informacje o certyfikacie",
"Resolver Server": "Server resolver",
"Resource Record Type": "Typ rekordu zasobów",
"Last Result": "Ostatni rekord",
"Last Result": "Ostatni wynik",
"Create your admin account": "Utwórz swoje konto administratora",
"Repeat Password": "Powtórz hasło",
respTime: "Odp. Czas (ms)",
respTime: "Czas odp. (ms)",
notAvailableShort: "N/A",
Create: "Create"
Create: "Stwórz"
}

View File

@ -109,5 +109,11 @@ export default {
"Repeat Password": "Повторите пароль",
respTime: "Resp. Time (ms)",
notAvailableShort: "N/A",
Create: "Create"
Create: "Create",
clearEventsMsg: "Are you sure want to delete all events for this monitor?",
clearHeartbeatsMsg: "Are you sure want to delete all heartbeats for this monitor?",
confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?",
"Clear Data": "Clear Data",
Events: "Events",
Heartbeats: "Heartbeats"
}

View File

@ -109,5 +109,11 @@ export default {
"Repeat Password": "Ponovite lozinku",
respTime: "Vreme odg. (ms)",
notAvailableShort: "N/A",
Create: "Create"
Create: "Create",
clearEventsMsg: "Are you sure want to delete all events for this monitor?",
clearHeartbeatsMsg: "Are you sure want to delete all heartbeats for this monitor?",
confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?",
"Clear Data": "Clear Data",
Events: "Events",
Heartbeats: "Heartbeats"
}

View File

@ -109,5 +109,11 @@ export default {
"Repeat Password": "Поновите лозинку",
respTime: "Време одг. (мс)",
notAvailableShort: "N/A",
Create: "Create"
Create: "Create",
clearEventsMsg: "Are you sure want to delete all events for this monitor?",
clearHeartbeatsMsg: "Are you sure want to delete all heartbeats for this monitor?",
confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?",
"Clear Data": "Clear Data",
Events: "Events",
Heartbeats: "Heartbeats"
}

View File

@ -109,5 +109,11 @@ export default {
"Repeat Password": "Upprepa Lösenord",
respTime: "Svarstid (ms)",
notAvailableShort: "Ej Tillg.",
Create: "Create"
Create: "Create",
clearEventsMsg: "Are you sure want to delete all events for this monitor?",
clearHeartbeatsMsg: "Are you sure want to delete all heartbeats for this monitor?",
confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?",
"Clear Data": "Clear Data",
Events: "Events",
Heartbeats: "Heartbeats"
}

View File

@ -109,5 +109,11 @@ export default {
"Repeat Password": "重复密码",
respTime: "Resp. Time (ms)",
notAvailableShort: "N/A",
Create: "Create"
Create: "Create",
clearEventsMsg: "Are you sure want to delete all events for this monitor?",
clearHeartbeatsMsg: "Are you sure want to delete all heartbeats for this monitor?",
confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?",
"Clear Data": "Clear Data",
Events: "Events",
Heartbeats: "Heartbeats"
}

View File

@ -109,5 +109,11 @@ export default {
"Repeat Password": "重複密碼",
respTime: "反應時間 (ms)",
notAvailableShort: "N/A",
Create: "建立"
Create: "建立",
clearEventsMsg: "是否確定刪除這個監測器的所有事件?",
clearHeartbeatsMsg: "是否確定刪除這個監測器的所有脈搏資料?",
confirmClearStatisticsMsg: "是否確定刪除所有監測器的脈搏資料?(您的監測器會繼續正常運作)",
"Clear Data": "清除資料",
Events: "事件",
Heartbeats: "脈搏"
}

View File

@ -28,7 +28,7 @@ import zhHK from "./languages/zh-HK";
import deDE from "./languages/de-DE";
import nlNL from "./languages/nl-NL";
import esEs from "./languages/es-ES";
import fr from "./languages/fr";
import frFR from "./languages/fr-FR";
import ja from "./languages/ja";
import daDK from "./languages/da-DK";
import sr from "./languages/sr";
@ -106,7 +106,7 @@ const languageList = {
"de-DE": deDE,
"nl-NL": nlNL,
"es-ES": esEs,
"fr": fr,
"fr-FR": frFR,
"ja": ja,
"da-DK": daDK,
"sr": sr,

View File

@ -107,8 +107,8 @@ export default {
}
});
socket.on("heartbeatList", (monitorID, data) => {
if (! (monitorID in this.heartbeatList)) {
socket.on("heartbeatList", (monitorID, data, overwrite = false) => {
if (! (monitorID in this.heartbeatList) || overwrite) {
this.heartbeatList[monitorID] = data;
} else {
this.heartbeatList[monitorID] = data.concat(this.heartbeatList[monitorID])
@ -127,8 +127,8 @@ export default {
this.certInfoList[monitorID] = JSON.parse(data)
});
socket.on("importantHeartbeatList", (monitorID, data) => {
if (! (monitorID in this.importantHeartbeatList)) {
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])
@ -254,6 +254,17 @@ export default {
this.importantHeartbeatList = {}
},
clearEvents(monitorID, callback) {
socket.emit("clearEvents", monitorID, callback)
},
clearHeartbeats(monitorID, callback) {
socket.emit("clearHeartbeats", monitorID, callback)
},
clearStatistics(callback) {
socket.emit("clearStatistics", callback)
},
},
computed: {

View File

@ -133,6 +133,23 @@
</div>
<div class="shadow-box table-shadow-box">
<div class="dropdown dropdown-clear-data">
<button class="btn btn-sm btn-outline-danger dropdown-toggle" type="button" data-bs-toggle="dropdown">
<font-awesome-icon icon="trash" /> {{ $t("Clear Data") }}
</button>
<ul class="dropdown-menu dropdown-menu-end">
<li>
<button type="button" class="dropdown-item" @click="clearEventsDialog">
{{ $t("Events") }}
</button>
</li>
<li>
<button type="button" class="dropdown-item" @click="clearHeartbeatsDialog">
{{ $t("Heartbeats") }}
</button>
</li>
</ul>
</div>
<table class="table table-borderless table-hover">
<thead>
<tr>
@ -172,6 +189,14 @@
<Confirm ref="confirmDelete" btn-style="btn-danger" :yes-text="$t('Yes')" :no-text="$t('No')" @yes="deleteMonitor">
{{ $t("deleteMonitorMsg") }}
</Confirm>
<Confirm ref="confirmClearEvents" btn-style="btn-danger" :yes-text="$t('Yes')" :no-text="$t('No')" @yes="clearEvents">
{{ $t("clearEventsMsg") }}
</Confirm>
<Confirm ref="confirmClearHeartbeats" btn-style="btn-danger" :yes-text="$t('Yes')" :no-text="$t('No')" @yes="clearHeartbeats">
{{ $t("clearHeartbeatsMsg") }}
</Confirm>
</div>
</transition>
</template>
@ -251,6 +276,7 @@ export default {
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]
}
@ -313,6 +339,14 @@ export default {
this.$refs.confirmDelete.show();
},
clearEventsDialog() {
this.$refs.confirmClearEvents.show();
},
clearHeartbeatsDialog() {
this.$refs.confirmClearHeartbeats.show();
},
deleteMonitor() {
this.$root.deleteMonitor(this.monitor.id, (res) => {
if (res.ok) {
@ -324,6 +358,21 @@ export default {
})
},
clearEvents() {
this.$root.clearEvents(this.monitor.id, (res) => {
if (! res.ok) {
toast.error(res.msg);
}
})
},
clearHeartbeats() {
this.$root.clearHeartbeats(this.monitor.id, (res) => {
if (! res.ok) {
toast.error(res.msg);
}
})
},
},
}
</script>
@ -340,16 +389,20 @@ export default {
@media (max-width: 550px) {
.functions {
text-align: center;
}
button, a {
margin-left: 10px !important;
margin-right: 10px !important;
}
}
.ping-chart-wrapper {
padding: 10px !important;
}
.dropdown-clear-data {
margin-bottom: 10px;
}
}
@media (max-width: 400px) {
@ -364,6 +417,13 @@ export default {
padding-left: 25px;
padding-right: 25px;
}
.dropdown-clear-data {
button {
display: block;
padding-top: 4px;
}
}
}
.url {
@ -417,10 +477,30 @@ table {
color: black;
}
.dropdown-clear-data {
float: right;
}
.dark {
.keyword {
color: $dark-font-color;
}
.dropdown-clear-data {
ul {
background-color: $dark-bg;
border-color: $dark-bg2;
border-width: 2px;
li button {
color: $dark-font-color;
}
li button:hover {
background-color: $dark-bg2;
}
}
}
}
</style>

View File

@ -126,6 +126,7 @@
<button v-if="settings.disableAuth" class="btn btn-outline-primary me-1" @click="enableAuth">{{ $t("Enable Auth") }}</button>
<button v-if="! settings.disableAuth" class="btn btn-primary me-1" @click="confirmDisableAuth">{{ $t("Disable Auth") }}</button>
<button v-if="! settings.disableAuth" class="btn btn-danger me-1" @click="$root.logout">{{ $t("Logout") }}</button>
<button class="btn btn-outline-danger me-1" @click="confirmClearStatistics">{{ $t("Clear all Statistics") }}</button>
</div>
</template>
</div>
@ -219,6 +220,10 @@
<p>Proszę używać ostrożnie.</p>
</template>
</Confirm>
<Confirm ref="confirmClearStatistics" btn-style="btn-danger" :yes-text="$t('Yes')" :no-text="$t('No')" @yes="clearStatistics">
{{ $t("confirmClearStatisticsMsg") }}
</Confirm>
</div>
</transition>
</template>
@ -317,6 +322,10 @@ export default {
this.$refs.confirmDisableAuth.show();
},
confirmClearStatistics() {
this.$refs.confirmClearStatistics.show();
},
disableAuth() {
this.settings.disableAuth = true;
this.saveSettings();
@ -328,6 +337,15 @@ export default {
this.$root.storage().removeItem("token");
},
clearStatistics() {
this.$root.clearStatistics((res) => {
if (res.ok) {
this.$router.go();
} else {
toast.error(res.msg);
}
})
},
},
}
</script>