mirror of
https://github.com/louislam/uptime-kuma.git
synced 2025-04-20 07:36:05 -04:00
Merge remote-tracking branch 'upstream/master'
# Conflicts: # docker-compose.yml # dockerfile # package-lock.json # package.json # server/notification.js # server/server.js # src/assets/vars.scss
This commit is contained in:
commit
7cabafebaf
@ -13,3 +13,6 @@ trim_trailing_whitespace = false
|
||||
|
||||
[*.yaml]
|
||||
indent_size = 2
|
||||
|
||||
[*.yml]
|
||||
indent_size = 2
|
||||
|
BIN
db/kuma.db
BIN
db/kuma.db
Binary file not shown.
BIN
public/icon.png
Normal file
BIN
public/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
@ -1,5 +1,5 @@
|
||||
const axios = require("axios");
|
||||
const { R } = require("redbean-node");
|
||||
const {R} = require("redbean-node");
|
||||
const FormData = require('form-data');
|
||||
const nodemailer = require("nodemailer");
|
||||
|
||||
@ -52,62 +52,64 @@ class Notification {
|
||||
} else if (notification.type === "smtp") {
|
||||
return await Notification.smtp(notification, msg)
|
||||
|
||||
} else if (notification.type === "signal") {
|
||||
try {
|
||||
let data = {
|
||||
"message": msg,
|
||||
"number": notification.signalNumber,
|
||||
"recipients": notification.signalRecipients.replace(/\s/g, '').split(",")
|
||||
};
|
||||
let config = {};
|
||||
|
||||
let res = await axios.post(notification.signalURL, data, config)
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
return false;
|
||||
}
|
||||
|
||||
} else if (notification.type === "discord") {
|
||||
try {
|
||||
// If heartbeatJSON is null, assume we're testing.
|
||||
if (heartbeatJSON == null) {
|
||||
let data = {
|
||||
username: 'Uptime-Kuma',
|
||||
content: msg
|
||||
}
|
||||
let res = await axios.post(notification.discordWebhookUrl, data)
|
||||
return true;
|
||||
}
|
||||
// If heartbeatJSON is not null, we go into the normal alerting loop.
|
||||
if (heartbeatJSON['status'] == 0) {
|
||||
var alertColor = "16711680";
|
||||
} else if (heartbeatJSON['status'] == 1) {
|
||||
var alertColor = "65280";
|
||||
}
|
||||
// If heartbeatJSON is null, assume we're testing.
|
||||
if(heartbeatJSON == null) {
|
||||
let data = {
|
||||
username: 'Uptime-Kuma',
|
||||
embeds: [{
|
||||
title: "Uptime-Kuma Alert",
|
||||
color: alertColor,
|
||||
fields: [
|
||||
{
|
||||
name: "Time (UTC)",
|
||||
value: heartbeatJSON["time"]
|
||||
},
|
||||
{
|
||||
name: "Message",
|
||||
value: msg
|
||||
}
|
||||
]
|
||||
}]
|
||||
username: 'Uptime-Kuma',
|
||||
content: msg
|
||||
}
|
||||
let res = await axios.post(notification.discordWebhookUrl, data)
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
return false;
|
||||
}
|
||||
// If heartbeatJSON is not null, we go into the normal alerting loop.
|
||||
if(heartbeatJSON['status'] == 0) {
|
||||
var alertColor = "16711680";
|
||||
} else if(heartbeatJSON['status'] == 1) {
|
||||
var alertColor = "65280";
|
||||
}
|
||||
let data = {
|
||||
username: 'Uptime-Kuma',
|
||||
embeds: [{
|
||||
title: "Uptime-Kuma Alert",
|
||||
color: alertColor,
|
||||
fields: [
|
||||
{
|
||||
name: "Time (UTC)",
|
||||
value: heartbeatJSON["time"]
|
||||
},
|
||||
{
|
||||
name: "Message",
|
||||
value: msg
|
||||
}
|
||||
]
|
||||
}]
|
||||
}
|
||||
let res = await axios.post(notification.discordWebhookUrl, data)
|
||||
return true;
|
||||
} catch(error) {
|
||||
console.log(error)
|
||||
return false;
|
||||
}
|
||||
return await Notification.discord(notification, msg)
|
||||
|
||||
} else if (notification.type === "signal") {
|
||||
try {
|
||||
let data = {
|
||||
"message": msg,
|
||||
"number": notification.signalNumber,
|
||||
"recipients": notification.signalRecipients.replace(/\s/g, '').split(",")
|
||||
};
|
||||
let config = {};
|
||||
|
||||
let res = await axios.post(notification.signalURL, data, config)
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
return false;
|
||||
}
|
||||
|
||||
} else {
|
||||
throw new Error("Notification type is not supported")
|
||||
}
|
||||
@ -122,7 +124,7 @@ class Notification {
|
||||
userID,
|
||||
])
|
||||
|
||||
if (!bean) {
|
||||
if (! bean) {
|
||||
throw new Error("notification not found")
|
||||
}
|
||||
|
||||
@ -142,7 +144,7 @@ class Notification {
|
||||
userID,
|
||||
])
|
||||
|
||||
if (!bean) {
|
||||
if (! bean) {
|
||||
throw new Error("notification not found")
|
||||
}
|
||||
|
||||
@ -171,6 +173,18 @@ class Notification {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static async discord(notification, msg) {
|
||||
const client = new Discord.Client();
|
||||
await client.login(notification.discordToken)
|
||||
|
||||
const channel = await client.channels.fetch(notification.discordChannelID);
|
||||
await channel.send(msg);
|
||||
|
||||
client.destroy()
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
@ -5,20 +5,18 @@ const server = http.createServer(app);
|
||||
const { Server } = require("socket.io");
|
||||
const io = new Server(server);
|
||||
const dayjs = require("dayjs");
|
||||
const { R } = require("redbean-node");
|
||||
const {R} = require("redbean-node");
|
||||
const passwordHash = require('password-hash');
|
||||
const jwt = require('jsonwebtoken');
|
||||
const Monitor = require("./model/monitor");
|
||||
const fs = require("fs");
|
||||
const { getSettings } = require("./util-server");
|
||||
const { Notification } = require("./notification")
|
||||
const {getSettings} = require("./util-server");
|
||||
const {Notification} = require("./notification")
|
||||
const args = require('args-parser')(process.argv);
|
||||
|
||||
console.log("args:")
|
||||
console.log(args)
|
||||
|
||||
const version = require('../package.json').version;
|
||||
const hostname = args.host || "0.0.0.0"
|
||||
const port = args.port || 50013
|
||||
const port = args.port || 3001
|
||||
|
||||
app.use(express.json())
|
||||
|
||||
@ -32,18 +30,16 @@ let needSetup = false;
|
||||
|
||||
app.use('/', express.static("dist"));
|
||||
|
||||
app.post('/test-webhook', function (request, response, next) {
|
||||
console.log("Test Webhook (application/json only)")
|
||||
console.log("Content-Type: " + request.header("Content-Type"))
|
||||
console.log(request.body)
|
||||
response.end();
|
||||
});
|
||||
|
||||
app.get('*', function (request, response, next) {
|
||||
app.get('*', function(request, response, next) {
|
||||
response.sendFile(process.cwd() + '/dist/index.html');
|
||||
});
|
||||
|
||||
io.on('connection', async (socket) => {
|
||||
|
||||
socket.emit("info", {
|
||||
version,
|
||||
})
|
||||
|
||||
console.log('a user connected');
|
||||
totalClient++;
|
||||
|
||||
@ -194,7 +190,7 @@ let needSetup = false;
|
||||
try {
|
||||
checkLogin(socket)
|
||||
|
||||
let bean = await R.findOne("monitor", " id = ? ", [monitor.id])
|
||||
let bean = await R.findOne("monitor", " id = ? ", [ monitor.id ])
|
||||
|
||||
if (bean.user_id !== socket.userID) {
|
||||
throw new Error("Permission denied.")
|
||||
@ -332,7 +328,7 @@ let needSetup = false;
|
||||
try {
|
||||
checkLogin(socket)
|
||||
|
||||
if (!password.currentPassword) {
|
||||
if (! password.currentPassword) {
|
||||
throw new Error("Invalid new password")
|
||||
}
|
||||
|
||||
@ -471,7 +467,7 @@ async function checkOwner(userID, monitorID) {
|
||||
userID,
|
||||
])
|
||||
|
||||
if (!row) {
|
||||
if (! row) {
|
||||
throw new Error("You do not own this monitor.");
|
||||
}
|
||||
}
|
||||
@ -526,7 +522,7 @@ async function getMonitorJSONList(userID) {
|
||||
}
|
||||
|
||||
function checkLogin(socket) {
|
||||
if (!socket.userID) {
|
||||
if (! socket.userID) {
|
||||
throw new Error("You are not logged in.");
|
||||
}
|
||||
}
|
||||
@ -534,7 +530,7 @@ function checkLogin(socket) {
|
||||
async function initDatabase() {
|
||||
const path = './data/kuma.db';
|
||||
|
||||
if (!fs.existsSync(path)) {
|
||||
if (! fs.existsSync(path)) {
|
||||
console.log("Copy Database")
|
||||
fs.copyFileSync("./db/kuma.db", path);
|
||||
}
|
||||
@ -551,7 +547,7 @@ async function initDatabase() {
|
||||
"jwtSecret"
|
||||
]);
|
||||
|
||||
if (!jwtSecretBean) {
|
||||
if (! jwtSecretBean) {
|
||||
console.log("JWT secret is not found, generate one.")
|
||||
jwtSecretBean = R.dispense("setting")
|
||||
jwtSecretBean.key = "jwtSecret"
|
||||
@ -638,7 +634,7 @@ async function sendHeartbeatList(socket, monitorID) {
|
||||
let result = [];
|
||||
|
||||
for (let bean of list) {
|
||||
result.unshift(bean.toJSON())
|
||||
result.unshift(bean.toJSON())
|
||||
}
|
||||
|
||||
socket.emit("heartbeatList", monitorID, result)
|
||||
|
@ -1,46 +1,16 @@
|
||||
@import "vars.scss";
|
||||
@import "node_modules/bootstrap/scss/bootstrap";
|
||||
|
||||
html,
|
||||
body,
|
||||
input,
|
||||
.modal-content {
|
||||
background: var(--page-background);
|
||||
color: var(--main-font-color);
|
||||
}
|
||||
a,
|
||||
.table,
|
||||
.nav-link {
|
||||
color: var(--main-font-color);
|
||||
}
|
||||
.nav-pills .nav-link.active,
|
||||
.nav-pills .show > .nav-link {
|
||||
color: #0a0a0a;
|
||||
}
|
||||
|
||||
.nav-link:hover,
|
||||
.nav-link:focus {
|
||||
color: #5cdd8b;
|
||||
}
|
||||
|
||||
.form-control,
|
||||
.form-control:focus,
|
||||
.form-select,
|
||||
.form-select:focus {
|
||||
color: var(--main-font-color);
|
||||
background-color: var(--background-4);
|
||||
}
|
||||
|
||||
#app {
|
||||
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
|
||||
segoe ui, Roboto, helvetica neue, Arial, noto sans, sans-serif,
|
||||
apple color emoji, segoe ui emoji, segoe ui symbol, noto color emoji;
|
||||
font-family: ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,segoe ui,Roboto,helvetica neue,Arial,noto sans,sans-serif,apple color emoji,segoe ui emoji,segoe ui symbol,noto color emoji;
|
||||
}
|
||||
|
||||
.shadow-box {
|
||||
overflow: hidden;
|
||||
box-shadow: 0 15px 70px rgba(0, 0, 0, 0.1);
|
||||
box-shadow: 0 15px 70px rgba(0, 0, 0, .1);
|
||||
padding: 10px;
|
||||
border-radius: 10px;
|
||||
|
||||
&.big-padding {
|
||||
padding: 20px;
|
||||
}
|
||||
@ -52,14 +22,10 @@ a,
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
// color: white;
|
||||
color: #0a0a0a;
|
||||
color: white;
|
||||
|
||||
&:hover,
|
||||
&:active,
|
||||
&:focus,
|
||||
&.active {
|
||||
color: #0a0a0a;
|
||||
&:hover, &:active, &:focus, &.active {
|
||||
color: white;
|
||||
background-color: $highlight;
|
||||
border-color: $highlight;
|
||||
}
|
||||
@ -70,8 +36,3 @@ a,
|
||||
backdrop-filter: blur(3px);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
a:hover {
|
||||
color: #7ce8a4;
|
||||
}
|
||||
}
|
||||
|
@ -159,7 +159,7 @@ export default {
|
||||
border-radius: 50rem;
|
||||
|
||||
&.empty {
|
||||
background-color: var(--background-ternary);
|
||||
background-color: aliceblue;
|
||||
}
|
||||
|
||||
&.down {
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<span class="badge rounded-pill" :class="'bg-' + color">{{ text }}</span>
|
||||
<span class="badge rounded-pill" :class=" 'bg-' + color ">{{ text }}</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@ -33,10 +33,7 @@ export default {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
span {
|
||||
width: 45px;
|
||||
}
|
||||
.badge {
|
||||
color: #0a0a0a;
|
||||
}
|
||||
span {
|
||||
width: 45px;
|
||||
}
|
||||
</style>
|
||||
|
@ -57,7 +57,5 @@ export default {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.badge{
|
||||
color: #0a0a0a;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
<!-- Desktop header -->
|
||||
<header class="d-flex flex-wrap justify-content-center py-3 mb-3 border-bottom" v-if="! $root.isMobile">
|
||||
<router-link to="/dashboard" class="d-flex align-items-center mb-3 mb-md-0 me-md-auto text-decoration-none">
|
||||
<router-link to="/dashboard" class="d-flex align-items-center mb-3 mb-md-0 me-md-auto text-dark text-decoration-none">
|
||||
<object class="bi me-2 ms-4" width="40" height="40" data="/icon.svg"></object>
|
||||
<span class="fs-4 title">Uptime Kuma</span>
|
||||
</router-link>
|
||||
@ -21,7 +21,7 @@
|
||||
|
||||
<!-- Mobile header -->
|
||||
<header class="d-flex flex-wrap justify-content-center mt-3 mb-3" v-else>
|
||||
<router-link to="/dashboard" class="d-flex align-items-center text-decoration-none">
|
||||
<router-link to="/dashboard" class="d-flex align-items-center text-dark text-decoration-none">
|
||||
<object class="bi" width="40" height="40" data="/icon.svg"></object>
|
||||
<span class="fs-4 title ms-2">Uptime Kuma</span>
|
||||
</router-link>
|
||||
@ -33,6 +33,14 @@
|
||||
<Login v-if="! $root.loggedIn && $root.allowLoginDialog" />
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<div class="container-fluid">
|
||||
Uptime Kuma -
|
||||
Version: {{ $root.info.version }} -
|
||||
<a href="https://github.com/louislam/uptime-kuma/releases" target="_blank">Check Update On GitHub</a>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- Mobile Only -->
|
||||
<div style="width: 100%;height: 60px;" v-if="$root.isMobile"></div>
|
||||
<nav class="bottom-nav" v-if="$root.isMobile">
|
||||
@ -87,7 +95,7 @@ export default {
|
||||
height: 60px;
|
||||
width: 100%;
|
||||
left: 0;
|
||||
background-color: var(--background-secondary);
|
||||
background-color: #fff;
|
||||
box-shadow: 0 15px 47px 0 rgba(0, 0, 0, 0.05), 0 5px 14px 0 rgba(0, 0, 0, 0.05);
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
@ -130,6 +138,14 @@ export default {
|
||||
}
|
||||
|
||||
main {
|
||||
margin-bottom: 30px;
|
||||
|
||||
}
|
||||
|
||||
footer {
|
||||
color: #AAA;
|
||||
font-size: 13px;
|
||||
margin-bottom: 30px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
@ -9,6 +9,7 @@ export default {
|
||||
|
||||
data() {
|
||||
return {
|
||||
info: { },
|
||||
socket: {
|
||||
token: null,
|
||||
firstConnect: true,
|
||||
@ -39,6 +40,10 @@ export default {
|
||||
transports: ['websocket']
|
||||
});
|
||||
|
||||
socket.on('info', (info) => {
|
||||
this.info = info;
|
||||
});
|
||||
|
||||
socket.on('setup', (monitorID, data) => {
|
||||
this.$router.push("/setup")
|
||||
});
|
||||
@ -275,6 +280,13 @@ export default {
|
||||
|
||||
watch: {
|
||||
|
||||
// Reload the SPA if the server version is changed.
|
||||
"info.version"(to, from) {
|
||||
if (from && from !== to) {
|
||||
window.location.reload()
|
||||
}
|
||||
},
|
||||
|
||||
remember() {
|
||||
localStorage.remember = (this.remember) ? "1" : "0"
|
||||
}
|
||||
|
@ -129,19 +129,17 @@ export default {
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: var(--main-font-color);
|
||||
background-color: var(--background-4);
|
||||
background-color: $highlight-white;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: var(--background-secondary);
|
||||
background-color: #cdf8f4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.badge {
|
||||
min-width: 58px;
|
||||
color: #0a0a0a;
|
||||
}
|
||||
|
||||
.small-padding {
|
||||
|
@ -362,22 +362,29 @@ const aryIannaTimeZones = [
|
||||
'Pacific/Efate',
|
||||
'Pacific/Wallis',
|
||||
'Pacific/Apia',
|
||||
'Africa/Johannesburg'
|
||||
'Africa/Johannesburg',
|
||||
];
|
||||
|
||||
|
||||
export function timezoneList() {
|
||||
|
||||
let result = [];
|
||||
|
||||
for (let timezone of aryIannaTimeZones) {
|
||||
|
||||
let display = dayjs().tz(timezone).format("Z");
|
||||
try {
|
||||
let display = dayjs().tz(timezone).format("Z");
|
||||
|
||||
result.push({
|
||||
name: `(UTC${display}) ${timezone}`,
|
||||
value: timezone,
|
||||
time: getTimezoneOffset(timezone),
|
||||
})
|
||||
} catch (e) {
|
||||
console.log(e.message);
|
||||
console.log("Skip this timezone")
|
||||
}
|
||||
|
||||
result.push({
|
||||
name: `(UTC${display}) ${timezone}`,
|
||||
value: timezone,
|
||||
time: getTimezoneOffset(timezone),
|
||||
})
|
||||
}
|
||||
|
||||
result.sort((a, b) => {
|
||||
|
Loading…
x
Reference in New Issue
Block a user