mirror of
https://github.com/louislam/uptime-kuma.git
synced 2024-12-23 15:40:25 -06:00
Merge branch 'master' into Spiritreader_master
# Conflicts: # src/pages/EditMonitor.vue
This commit is contained in:
commit
86e18ac11d
10
.github/ISSUE_TEMPLATE/--please-go-to--discussion--tab-if-you-want-to-ask-or-share-something.md
vendored
Normal file
10
.github/ISSUE_TEMPLATE/--please-go-to--discussion--tab-if-you-want-to-ask-or-share-something.md
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
name: ⚠ Please go to "Discussions" Tab if you want to ask or share something
|
||||||
|
about: BUG REPORT ONLY HERE
|
||||||
|
title: ''
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
BUG REPORT ONLY HERE
|
34
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
34
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Create a report to help us improve
|
||||||
|
title: ''
|
||||||
|
labels: bug
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Describe the bug**
|
||||||
|
A clear and concise description of what the bug is.
|
||||||
|
|
||||||
|
**To Reproduce**
|
||||||
|
Steps to reproduce the behavior:
|
||||||
|
1. Go to '...'
|
||||||
|
2. Click on '....'
|
||||||
|
3. Scroll down to '....'
|
||||||
|
4. See error
|
||||||
|
|
||||||
|
**Expected behavior**
|
||||||
|
A clear and concise description of what you expected to happen.
|
||||||
|
|
||||||
|
**Screenshots**
|
||||||
|
If applicable, add screenshots to help explain your problem.
|
||||||
|
|
||||||
|
**Desktop (please complete the following information):**
|
||||||
|
- Uptime Kuma Version:
|
||||||
|
- Using Docker?: Yes/No
|
||||||
|
- OS:
|
||||||
|
- Browser:
|
||||||
|
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context about the problem here.
|
@ -80,12 +80,17 @@ PS: For every new release, it takes some time to build the docker image, please
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
git fetch --all
|
git fetch --all
|
||||||
git checkout 1.0.5 --force
|
git checkout 1.0.6 --force
|
||||||
npm install
|
npm install
|
||||||
npm run build
|
npm run build
|
||||||
pm2 restart uptime-kuma
|
pm2 restart uptime-kuma
|
||||||
```
|
```
|
||||||
|
|
||||||
|
# What's Next?
|
||||||
|
|
||||||
|
I will mark requests/issues to the next milestone.
|
||||||
|
https://github.com/louislam/uptime-kuma/milestones
|
||||||
|
|
||||||
# More Screenshots
|
# More Screenshots
|
||||||
|
|
||||||
Settings Page:
|
Settings Page:
|
||||||
@ -112,7 +117,7 @@ If you love this project, please consider giving me a ⭐.
|
|||||||
|
|
||||||
# Contribute
|
# Contribute
|
||||||
|
|
||||||
If you want to report a bug or request a new featue. Free feel to open a new issue.
|
If you want to report a bug or request a new feature. Free feel to open a new issue.
|
||||||
|
|
||||||
If you want to modify Uptime Kuma, this guideline maybe useful for you: https://github.com/louislam/uptime-kuma/wiki/%5BDev%5D-Setup-Development-Environment
|
If you want to modify Uptime Kuma, this guideline maybe useful for you: https://github.com/louislam/uptime-kuma/wiki/%5BDev%5D-Setup-Development-Environment
|
||||||
|
|
||||||
|
37
db/patch1.sql
Normal file
37
db/patch1.sql
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db.
|
||||||
|
-- Change Monitor.created_date from "TIMESTAMP" to "DATETIME"
|
||||||
|
-- SQL Generated by Intellij Idea
|
||||||
|
PRAGMA foreign_keys=off;
|
||||||
|
|
||||||
|
BEGIN TRANSACTION;
|
||||||
|
|
||||||
|
create table monitor_dg_tmp
|
||||||
|
(
|
||||||
|
id INTEGER not null
|
||||||
|
primary key autoincrement,
|
||||||
|
name VARCHAR(150),
|
||||||
|
active BOOLEAN default 1 not null,
|
||||||
|
user_id INTEGER
|
||||||
|
references user
|
||||||
|
on update cascade on delete set null,
|
||||||
|
interval INTEGER default 20 not null,
|
||||||
|
url TEXT,
|
||||||
|
type VARCHAR(20),
|
||||||
|
weight INTEGER default 2000,
|
||||||
|
hostname VARCHAR(255),
|
||||||
|
port INTEGER,
|
||||||
|
created_date DATETIME,
|
||||||
|
keyword VARCHAR(255)
|
||||||
|
);
|
||||||
|
|
||||||
|
insert into monitor_dg_tmp(id, name, active, user_id, interval, url, type, weight, hostname, port, created_date, keyword) select id, name, active, user_id, interval, url, type, weight, hostname, port, created_date, keyword from monitor;
|
||||||
|
|
||||||
|
drop table monitor;
|
||||||
|
|
||||||
|
alter table monitor_dg_tmp rename to monitor;
|
||||||
|
|
||||||
|
create index user_id on monitor (user_id);
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
|
|
||||||
|
PRAGMA foreign_keys=on;
|
22
package-lock.json
generated
22
package-lock.json
generated
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "uptime-kuma",
|
"name": "uptime-kuma",
|
||||||
"version": "1.0.5",
|
"version": "1.0.6",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -392,6 +392,11 @@
|
|||||||
"follow-redirects": "^1.10.0"
|
"follow-redirects": "^1.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"babel-plugin-add-module-exports": {
|
||||||
|
"version": "0.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/babel-plugin-add-module-exports/-/babel-plugin-add-module-exports-0.2.1.tgz",
|
||||||
|
"integrity": "sha1-mumh9KjcZ/DN7E9K7aHkOl/2XiU="
|
||||||
|
},
|
||||||
"backo2": {
|
"backo2": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz",
|
||||||
@ -2093,6 +2098,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||||
"integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
|
"integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
|
||||||
},
|
},
|
||||||
|
"merge": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/merge/-/merge-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-jz+Cfrg9GWOZbQAnDQ4hlVnQky+341Yk5ru8bZSe6sIDTCIg8n9i/u7hSQGSVOF3C7lH6mGtqjkiT9G4wFLL0w=="
|
||||||
|
},
|
||||||
"merge-descriptors": {
|
"merge-descriptors": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
|
||||||
@ -3698,6 +3708,16 @@
|
|||||||
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
|
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
|
"v-pagination-3": {
|
||||||
|
"version": "0.1.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/v-pagination-3/-/v-pagination-3-0.1.6.tgz",
|
||||||
|
"integrity": "sha512-82J8HnEIYtZijn6F3xhyP/ildI5K7Rv4Yu74VNfQWQsiPWTKntgVvZgBH8UPh/lFEjgWxty/M4N+YHvS+YbGzg==",
|
||||||
|
"requires": {
|
||||||
|
"babel-plugin-add-module-exports": "^0.2.1",
|
||||||
|
"merge": "^2.1.1",
|
||||||
|
"vue": ">=3.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"v8flags": {
|
"v8flags": {
|
||||||
"version": "3.2.0",
|
"version": "3.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.2.0.tgz",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "uptime-kuma",
|
"name": "uptime-kuma",
|
||||||
"version": "1.0.5",
|
"version": "1.0.6",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@ -12,10 +12,10 @@
|
|||||||
"update": "",
|
"update": "",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"vite-preview-dist": "vite preview --host",
|
"vite-preview-dist": "vite preview --host",
|
||||||
"build-docker": "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.0.5 --target release . --push",
|
"build-docker": "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.0.6 --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": "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",
|
"build-docker-nightly-amd64": "docker buildx build --platform linux/amd64 -t louislam/uptime-kuma:nightly-amd64 --target nightly . --push",
|
||||||
"setup": "git checkout 1.0.5 && npm install && npm run build",
|
"setup": "git checkout 1.0.6 && npm install && npm run build",
|
||||||
"version-global-replace": "node extra/version-global-replace.js",
|
"version-global-replace": "node extra/version-global-replace.js",
|
||||||
"mark-as-nightly": "node extra/mark-as-nightly.js"
|
"mark-as-nightly": "node extra/mark-as-nightly.js"
|
||||||
},
|
},
|
||||||
@ -38,6 +38,7 @@
|
|||||||
"socket.io-client": "^4.1.3",
|
"socket.io-client": "^4.1.3",
|
||||||
"sqlite3": "^5.0.2",
|
"sqlite3": "^5.0.2",
|
||||||
"tcp-ping": "^0.1.1",
|
"tcp-ping": "^0.1.1",
|
||||||
|
"v-pagination-3": "^0.1.6",
|
||||||
"vue": "^3.0.5",
|
"vue": "^3.0.5",
|
||||||
"vue-confirm-dialog": "^1.0.2",
|
"vue-confirm-dialog": "^1.0.2",
|
||||||
"vue-router": "^4.0.10",
|
"vue-router": "^4.0.10",
|
||||||
|
119
server/database.js
Normal file
119
server/database.js
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
const fs = require("fs");
|
||||||
|
const {sleep} = require("./util");
|
||||||
|
const {R} = require("redbean-node");
|
||||||
|
const {setSetting, setting} = require("./util-server");
|
||||||
|
|
||||||
|
|
||||||
|
class Database {
|
||||||
|
|
||||||
|
static templatePath = "./db/kuma.db"
|
||||||
|
static path = './data/kuma.db';
|
||||||
|
static latestVersion = 1;
|
||||||
|
static noReject = true;
|
||||||
|
|
||||||
|
static async patch() {
|
||||||
|
let version = parseInt(await setting("database_version"));
|
||||||
|
|
||||||
|
if (! version) {
|
||||||
|
version = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.info("Your database version: " + version);
|
||||||
|
console.info("Latest database version: " + this.latestVersion);
|
||||||
|
|
||||||
|
if (version === this.latestVersion) {
|
||||||
|
console.info("Database no need to patch");
|
||||||
|
} else {
|
||||||
|
console.info("Database patch is needed")
|
||||||
|
|
||||||
|
console.info("Backup the db")
|
||||||
|
const backupPath = "./data/kuma.db.bak" + version;
|
||||||
|
fs.copyFileSync(Database.path, backupPath);
|
||||||
|
|
||||||
|
// Try catch anything here, if gone wrong, restore the backup
|
||||||
|
try {
|
||||||
|
for (let i = version + 1; i <= this.latestVersion; i++) {
|
||||||
|
const sqlFile = `./db/patch${i}.sql`;
|
||||||
|
console.info(`Patching ${sqlFile}`);
|
||||||
|
await Database.importSQLFile(sqlFile);
|
||||||
|
console.info(`Patched ${sqlFile}`);
|
||||||
|
await setSetting("database_version", i);
|
||||||
|
}
|
||||||
|
console.log("Database Patched Successfully");
|
||||||
|
} catch (ex) {
|
||||||
|
await Database.close();
|
||||||
|
console.error("Patch db failed!!! Restoring the backup")
|
||||||
|
fs.copyFileSync(backupPath, Database.path);
|
||||||
|
console.error(ex)
|
||||||
|
|
||||||
|
console.error("Start Uptime-Kuma failed due to patch db failed")
|
||||||
|
console.error("Please submit the bug report if you still encounter the problem after restart: https://github.com/louislam/uptime-kuma/issues")
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sadly, multi sql statements is not supported by many sqlite libraries, I have to implement it myself
|
||||||
|
* @param filename
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
static async importSQLFile(filename) {
|
||||||
|
|
||||||
|
await R.getCell("SELECT 1");
|
||||||
|
|
||||||
|
let text = fs.readFileSync(filename).toString();
|
||||||
|
|
||||||
|
// Remove all comments (--)
|
||||||
|
let lines = text.split("\n");
|
||||||
|
lines = lines.filter((line) => {
|
||||||
|
return ! line.startsWith("--")
|
||||||
|
});
|
||||||
|
|
||||||
|
// Split statements by semicolon
|
||||||
|
// Filter out empty line
|
||||||
|
text = lines.join("\n")
|
||||||
|
|
||||||
|
let statements = text.split(";")
|
||||||
|
.map((statement) => {
|
||||||
|
return statement.trim();
|
||||||
|
})
|
||||||
|
.filter((statement) => {
|
||||||
|
return statement !== "";
|
||||||
|
})
|
||||||
|
|
||||||
|
for (let statement of statements) {
|
||||||
|
await R.exec(statement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Special handle, because tarn.js throw a promise reject that cannot be caught
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
static async close() {
|
||||||
|
const listener = (reason, p) => {
|
||||||
|
Database.noReject = false;
|
||||||
|
};
|
||||||
|
process.addListener('unhandledRejection', listener);
|
||||||
|
|
||||||
|
console.log("Closing DB")
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
Database.noReject = true;
|
||||||
|
await R.close()
|
||||||
|
await sleep(2000)
|
||||||
|
|
||||||
|
if (Database.noReject) {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
console.log("Waiting to close the db")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log("SQLite closed")
|
||||||
|
|
||||||
|
process.removeListener('unhandledRejection', listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Database;
|
@ -3,8 +3,6 @@ const utc = require('dayjs/plugin/utc')
|
|||||||
var timezone = require('dayjs/plugin/timezone')
|
var timezone = require('dayjs/plugin/timezone')
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
dayjs.extend(timezone)
|
dayjs.extend(timezone)
|
||||||
const axios = require("axios");
|
|
||||||
const {R} = require("redbean-node");
|
|
||||||
const {BeanModel} = require("redbean-node/dist/bean-model");
|
const {BeanModel} = require("redbean-node/dist/bean-model");
|
||||||
|
|
||||||
|
|
||||||
|
@ -49,8 +49,6 @@ class Monitor extends BeanModel {
|
|||||||
let retries = 0;
|
let retries = 0;
|
||||||
|
|
||||||
const beat = async () => {
|
const beat = async () => {
|
||||||
console.log(`Monitor ${this.id}: Heartbeat`)
|
|
||||||
|
|
||||||
if (! previousBeat) {
|
if (! previousBeat) {
|
||||||
previousBeat = await R.findOne("heartbeat", " monitor_id = ? ORDER BY time DESC", [
|
previousBeat = await R.findOne("heartbeat", " monitor_id = ? ORDER BY time DESC", [
|
||||||
this.id
|
this.id
|
||||||
@ -131,8 +129,6 @@ class Monitor extends BeanModel {
|
|||||||
this.id
|
this.id
|
||||||
])
|
])
|
||||||
|
|
||||||
let promiseList = [];
|
|
||||||
|
|
||||||
let text;
|
let text;
|
||||||
if (bean.status === 1) {
|
if (bean.status === 1) {
|
||||||
text = "✅ Up"
|
text = "✅ Up"
|
||||||
@ -143,16 +139,24 @@ class Monitor extends BeanModel {
|
|||||||
let msg = `[${this.name}] [${text}] ${bean.msg}`;
|
let msg = `[${this.name}] [${text}] ${bean.msg}`;
|
||||||
|
|
||||||
for(let notification of notificationList) {
|
for(let notification of notificationList) {
|
||||||
promiseList.push(Notification.send(JSON.parse(notification.config), msg, await this.toJSON(), bean.toJSON()));
|
try {
|
||||||
|
await Notification.send(JSON.parse(notification.config), msg, await this.toJSON(), bean.toJSON())
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Cannot send notification to " + notification.name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await Promise.all(promiseList);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
bean.important = false;
|
bean.important = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (bean.status === 1) {
|
||||||
|
console.info(`Monitor #${this.id} '${this.name}': Successful Response: ${bean.ping} ms | Interval: ${this.interval} seconds | Type: ${this.type}`)
|
||||||
|
} else {
|
||||||
|
console.warn(`Monitor #${this.id} '${this.name}': Failing: ${bean.msg} | Type: ${this.type}`)
|
||||||
|
}
|
||||||
|
|
||||||
io.to(this.user_id).emit("heartbeat", bean.toJSON());
|
io.to(this.user_id).emit("heartbeat", bean.toJSON());
|
||||||
|
|
||||||
await R.store(bean)
|
await R.store(bean)
|
||||||
|
@ -72,7 +72,7 @@ class Notification {
|
|||||||
finalData = data;
|
finalData = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
let res = await axios.post(notification.webhookURL, finalData, config)
|
await axios.post(notification.webhookURL, finalData, config)
|
||||||
return okMsg;
|
return okMsg;
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -90,7 +90,7 @@ class Notification {
|
|||||||
username: 'Uptime-Kuma',
|
username: 'Uptime-Kuma',
|
||||||
content: msg
|
content: msg
|
||||||
}
|
}
|
||||||
let res = await axios.post(notification.discordWebhookUrl, data)
|
await axios.post(notification.discordWebhookUrl, data)
|
||||||
return okMsg;
|
return okMsg;
|
||||||
}
|
}
|
||||||
// If heartbeatJSON is not null, we go into the normal alerting loop.
|
// If heartbeatJSON is not null, we go into the normal alerting loop.
|
||||||
@ -116,7 +116,7 @@ class Notification {
|
|||||||
]
|
]
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
let res = await axios.post(notification.discordWebhookUrl, data)
|
await axios.post(notification.discordWebhookUrl, data)
|
||||||
return okMsg;
|
return okMsg;
|
||||||
} catch(error) {
|
} catch(error) {
|
||||||
throwGeneralAxiosError(error)
|
throwGeneralAxiosError(error)
|
||||||
@ -131,7 +131,7 @@ class Notification {
|
|||||||
};
|
};
|
||||||
let config = {};
|
let config = {};
|
||||||
|
|
||||||
let res = await axios.post(notification.signalURL, data, config)
|
await axios.post(notification.signalURL, data, config)
|
||||||
return okMsg;
|
return okMsg;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throwGeneralAxiosError(error)
|
throwGeneralAxiosError(error)
|
||||||
@ -141,7 +141,7 @@ class Notification {
|
|||||||
try {
|
try {
|
||||||
if (heartbeatJSON == null) {
|
if (heartbeatJSON == null) {
|
||||||
let data = {'text': "Uptime Kuma Slack testing successful.", 'channel': notification.slackchannel, 'username': notification.slackusername, 'icon_emoji': notification.slackiconemo}
|
let data = {'text': "Uptime Kuma Slack testing successful.", 'channel': notification.slackchannel, 'username': notification.slackusername, 'icon_emoji': notification.slackiconemo}
|
||||||
let res = await axios.post(notification.slackwebhookURL, data)
|
await axios.post(notification.slackwebhookURL, data)
|
||||||
return okMsg;
|
return okMsg;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -186,7 +186,7 @@ class Notification {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
let res = await axios.post(notification.slackwebhookURL, data)
|
await axios.post(notification.slackwebhookURL, data)
|
||||||
return okMsg;
|
return okMsg;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throwGeneralAxiosError(error)
|
throwGeneralAxiosError(error)
|
||||||
@ -199,7 +199,7 @@ class Notification {
|
|||||||
let data = {'message': "<b>Uptime Kuma Pushover testing successful.</b>",
|
let data = {'message': "<b>Uptime Kuma Pushover testing successful.</b>",
|
||||||
'user': notification.pushoveruserkey, 'token': notification.pushoverapptoken, 'sound':notification.pushoversounds,
|
'user': notification.pushoveruserkey, 'token': notification.pushoverapptoken, 'sound':notification.pushoversounds,
|
||||||
'priority': notification.pushoverpriority, 'title':notification.pushovertitle, 'retry': "30", 'expire':"3600", 'html': 1}
|
'priority': notification.pushoverpriority, 'title':notification.pushovertitle, 'retry': "30", 'expire':"3600", 'html': 1}
|
||||||
let res = await axios.post(pushoverlink, data)
|
await axios.post(pushoverlink, data)
|
||||||
return okMsg;
|
return okMsg;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -214,7 +214,7 @@ class Notification {
|
|||||||
"expire": "3600",
|
"expire": "3600",
|
||||||
"html": 1
|
"html": 1
|
||||||
}
|
}
|
||||||
let res = await axios.post(pushoverlink, data)
|
await axios.post(pushoverlink, data)
|
||||||
return okMsg;
|
return okMsg;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throwGeneralAxiosError(error)
|
throwGeneralAxiosError(error)
|
||||||
@ -278,7 +278,7 @@ class Notification {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// send mail with defined transport object
|
// send mail with defined transport object
|
||||||
let info = await transporter.sendMail({
|
await transporter.sendMail({
|
||||||
from: `"Uptime Kuma" <${notification.smtpFrom}>`,
|
from: `"Uptime Kuma" <${notification.smtpFrom}>`,
|
||||||
to: notification.smtpTo,
|
to: notification.smtpTo,
|
||||||
subject: msg,
|
subject: msg,
|
||||||
|
@ -12,6 +12,7 @@ const fs = require("fs");
|
|||||||
const {getSettings} = require("./util-server");
|
const {getSettings} = require("./util-server");
|
||||||
const {Notification} = require("./notification")
|
const {Notification} = require("./notification")
|
||||||
const gracefulShutdown = require('http-graceful-shutdown');
|
const gracefulShutdown = require('http-graceful-shutdown');
|
||||||
|
const Database = require("./database");
|
||||||
const {sleep} = require("./util");
|
const {sleep} = require("./util");
|
||||||
const args = require('args-parser')(process.argv);
|
const args = require('args-parser')(process.argv);
|
||||||
|
|
||||||
@ -27,9 +28,28 @@ const server = http.createServer(app);
|
|||||||
const io = new Server(server);
|
const io = new Server(server);
|
||||||
app.use(express.json())
|
app.use(express.json())
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Total WebSocket client connected to server currently, no actual use
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
let totalClient = 0;
|
let totalClient = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use for decode the auth object
|
||||||
|
* @type {null}
|
||||||
|
*/
|
||||||
let jwtSecret = null;
|
let jwtSecret = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main monitor list
|
||||||
|
* @type {{}}
|
||||||
|
*/
|
||||||
let monitorList = {};
|
let monitorList = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show Setup Page
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
let needSetup = false;
|
let needSetup = false;
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
@ -163,10 +183,6 @@ let needSetup = false;
|
|||||||
msg: e.message
|
msg: e.message
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Auth Only API
|
// Auth Only API
|
||||||
@ -556,19 +572,21 @@ function checkLogin(socket) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function initDatabase() {
|
async function initDatabase() {
|
||||||
const path = './data/kuma.db';
|
if (! fs.existsSync(Database.path)) {
|
||||||
|
|
||||||
if (! fs.existsSync(path)) {
|
|
||||||
console.log("Copying Database")
|
console.log("Copying Database")
|
||||||
fs.copyFileSync("./db/kuma.db", path);
|
fs.copyFileSync(Database.templatePath, Database.path);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("Connecting to Database")
|
console.log("Connecting to Database")
|
||||||
R.setup('sqlite', {
|
R.setup('sqlite', {
|
||||||
filename: path
|
filename: Database.path
|
||||||
});
|
});
|
||||||
console.log("Connected")
|
console.log("Connected")
|
||||||
|
|
||||||
|
// Patch the database
|
||||||
|
await Database.patch()
|
||||||
|
|
||||||
|
// Auto map the model to a bean object
|
||||||
R.freeze(true)
|
R.freeze(true)
|
||||||
await R.autoloadModels("./server/model");
|
await R.autoloadModels("./server/model");
|
||||||
|
|
||||||
@ -588,6 +606,7 @@ async function initDatabase() {
|
|||||||
console.log("Load JWT secret from database.")
|
console.log("Load JWT secret from database.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If there is no record in user table, it is a new Uptime Kuma instance, need to setup
|
||||||
if ((await R.count("user")) === 0) {
|
if ((await R.count("user")) === 0) {
|
||||||
console.log("No user, need setup")
|
console.log("No user, need setup")
|
||||||
needSetup = true;
|
needSetup = true;
|
||||||
@ -706,11 +725,6 @@ const startGracefulShutdown = async () => {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let noReject = true;
|
|
||||||
process.on('unhandledRejection', (reason, p) => {
|
|
||||||
noReject = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
async function shutdownFunction(signal) {
|
async function shutdownFunction(signal) {
|
||||||
console.log('Called signal: ' + signal);
|
console.log('Called signal: ' + signal);
|
||||||
|
|
||||||
@ -719,24 +733,8 @@ async function shutdownFunction(signal) {
|
|||||||
let monitor = monitorList[id]
|
let monitor = monitorList[id]
|
||||||
monitor.stop()
|
monitor.stop()
|
||||||
}
|
}
|
||||||
await sleep(2000)
|
await sleep(2000);
|
||||||
|
await Database.close();
|
||||||
console.log("Closing DB")
|
|
||||||
|
|
||||||
// Special handle, because tarn.js throw a promise reject that cannot be caught
|
|
||||||
while (true) {
|
|
||||||
noReject = true;
|
|
||||||
await R.close()
|
|
||||||
await sleep(2000)
|
|
||||||
|
|
||||||
if (noReject) {
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
console.log("Waiting...")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("OK")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function finalFunction() {
|
function finalFunction() {
|
||||||
|
@ -45,6 +45,18 @@ exports.setting = async function (key) {
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exports.setSetting = async function (key, value) {
|
||||||
|
let bean = await R.findOne("setting", " `key` = ? ", [
|
||||||
|
key
|
||||||
|
])
|
||||||
|
if (! bean) {
|
||||||
|
bean = R.dispense("setting")
|
||||||
|
bean.key = key;
|
||||||
|
}
|
||||||
|
bean.value = value;
|
||||||
|
await R.store(bean)
|
||||||
|
}
|
||||||
|
|
||||||
exports.getSettings = async function (type) {
|
exports.getSettings = async function (type) {
|
||||||
let list = await R.getAll("SELECT * FROM setting WHERE `type` = ? ", [
|
let list = await R.getAll("SELECT * FROM setting WHERE `type` = ? ", [
|
||||||
type
|
type
|
||||||
|
@ -4,9 +4,9 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import relativeTime from "dayjs/plugin/relativeTime"
|
import relativeTime from "dayjs/plugin/relativeTime"
|
||||||
import utc from 'dayjs/plugin/utc'
|
import utc from 'dayjs/plugin/utc'
|
||||||
import timezone from 'dayjs/plugin/timezone' // dependent on utc plugin
|
import timezone from 'dayjs/plugin/timezone' // dependent on utc plugin
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
dayjs.extend(timezone)
|
dayjs.extend(timezone)
|
||||||
dayjs.extend(relativeTime)
|
dayjs.extend(relativeTime)
|
||||||
|
@ -55,7 +55,7 @@
|
|||||||
<p style="margin-top: 8px;">
|
<p style="margin-top: 8px;">
|
||||||
|
|
||||||
<template v-if="notification.telegramBotToken">
|
<template v-if="notification.telegramBotToken">
|
||||||
<a :href="telegramGetUpdatesURL" target="_blank">{{ telegramGetUpdatesURL }}</a>
|
<a :href="telegramGetUpdatesURL" target="_blank" style="word-break: break-word;">{{ telegramGetUpdatesURL }}</a>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-else>
|
<template v-else>
|
||||||
@ -69,13 +69,13 @@
|
|||||||
<template v-if="notification.type === 'webhook'">
|
<template v-if="notification.type === 'webhook'">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="webhook-url" class="form-label">Post URL</label>
|
<label for="webhook-url" class="form-label">Post URL</label>
|
||||||
<input type="url" pattern="https?://.+" class="form-control" id="webhook-url" required v-model="notification.webhookURL">
|
<input type="url" pattern="https?://.+" class="form-control" id="webhook-url" required v-model="notification.webhookURL">
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="webhook-content-type" class="form-label">Content Type</label>
|
<label for="webhook-content-type" class="form-label">Content Type</label>
|
||||||
<select class="form-select" id="webhook-content-type" v-model="notification.webhookContentType" required>
|
<select class="form-select" id="webhook-content-type" v-model="notification.webhookContentType" required>
|
||||||
<option value="json">application/json</option>
|
<option value="json">application/json</option>
|
||||||
<option value="form-data">multipart/form-data</option>
|
<option value="form-data">multipart/form-data</option>
|
||||||
</select>
|
</select>
|
||||||
@ -100,7 +100,7 @@
|
|||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input class="form-check-input" type="checkbox" value="" id="secure" v-model="notification.smtpSecure">
|
<input class="form-check-input" type="checkbox" value="" id="secure" v-model="notification.smtpSecure">
|
||||||
<label class="form-check-label" for="secure">
|
<label class="form-check-label" for="secure">
|
||||||
Secure
|
Secure
|
||||||
</label>
|
</label>
|
||||||
@ -141,7 +141,7 @@
|
|||||||
<template v-if="notification.type === 'signal'">
|
<template v-if="notification.type === 'signal'">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="signal-url" class="form-label">Post URL</label>
|
<label for="signal-url" class="form-label">Post URL</label>
|
||||||
<input type="url" pattern="https?://.+" class="form-control" id="signal-url" required v-model="notification.signalURL">
|
<input type="url" pattern="https?://.+" class="form-control" id="signal-url" required v-model="notification.signalURL">
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
No Monitors, please <router-link to="/add">add one</router-link>.
|
No Monitors, please <router-link to="/add">add one</router-link>.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<router-link :to="monitorURL(item.id)" class="item" :class="{ 'disabled': ! item.active }" v-for="item in sortedMonitorList" @click="$root.cancelActiveList">
|
<router-link :to="monitorURL(item.id)" class="item" :class="{ 'disabled': ! item.active }" v-for="(item, index) in sortedMonitorList" @click="$root.cancelActiveList" :key="index">
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-6 col-md-8 small-padding">
|
<div class="col-6 col-md-8 small-padding">
|
||||||
|
@ -47,7 +47,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr v-for="beat in importantHeartBeatList">
|
<tr v-for="(beat, index) in displayedRecords" :key="index">
|
||||||
<td>{{ beat.name }}</td>
|
<td>{{ beat.name }}</td>
|
||||||
<td><Status :status="beat.status" /></td>
|
<td><Status :status="beat.status" /></td>
|
||||||
<td><Datetime :value="beat.time" /></td>
|
<td><Datetime :value="beat.time" /></td>
|
||||||
@ -59,6 +59,13 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-center kuma_pagination">
|
||||||
|
<pagination
|
||||||
|
v-model="page"
|
||||||
|
:records=importantHeartBeatList.length
|
||||||
|
:per-page="perPage" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -68,8 +75,21 @@
|
|||||||
<script>
|
<script>
|
||||||
import Status from "../components/Status.vue";
|
import Status from "../components/Status.vue";
|
||||||
import Datetime from "../components/Datetime.vue";
|
import Datetime from "../components/Datetime.vue";
|
||||||
|
import Pagination from "v-pagination-3";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {Datetime, Status},
|
components: {
|
||||||
|
Datetime,
|
||||||
|
Status,
|
||||||
|
Pagination,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
page: 1,
|
||||||
|
perPage: 25,
|
||||||
|
heartBeatList: [],
|
||||||
|
}
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
stats() {
|
stats() {
|
||||||
let result = {
|
let result = {
|
||||||
@ -129,8 +149,16 @@ export default {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.heartBeatList = result;
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
},
|
||||||
|
|
||||||
|
displayedRecords() {
|
||||||
|
const startIndex = this.perPage * (this.page - 1);
|
||||||
|
const endIndex = startIndex + this.perPage;
|
||||||
|
return this.heartBeatList.slice(startIndex, endIndex);
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
<div class="functions">
|
<div class="functions">
|
||||||
<button class="btn btn-light" @click="pauseDialog" v-if="monitor.active">Pause</button>
|
<button class="btn btn-light" @click="pauseDialog" v-if="monitor.active">Pause</button>
|
||||||
<button class="btn btn-primary" @click="resumeMonitor" v-if="! monitor.active">Resume</button>
|
<button class="btn btn-primary" @click="resumeMonitor" v-if="! monitor.active">Resume</button>
|
||||||
<router-link :to=" '/edit/' + monitor.id " class="btn btn-secondary">Edit</router-link>
|
<router-link :to=" '/edit/' + monitor.id " class="btn btn-secondary">Edit</router-link>
|
||||||
<button class="btn btn-danger" @click="deleteDialog">Delete</button>
|
<button class="btn btn-danger" @click="deleteDialog">Delete</button>
|
||||||
</div>
|
</div>
|
||||||
@ -64,7 +64,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr v-for="beat in importantHeartBeatList">
|
<tr v-for="(beat, index) in displayedRecords" :key="index">
|
||||||
<td><Status :status="beat.status" /></td>
|
<td><Status :status="beat.status" /></td>
|
||||||
<td><Datetime :value="beat.time" /></td>
|
<td><Datetime :value="beat.time" /></td>
|
||||||
<td>{{ beat.msg }}</td>
|
<td>{{ beat.msg }}</td>
|
||||||
@ -75,6 +75,13 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-center kuma_pagination">
|
||||||
|
<pagination
|
||||||
|
v-model="page"
|
||||||
|
:records=importantHeartBeatList.length
|
||||||
|
:per-page="perPage" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Confirm ref="confirmPause" @yes="pauseMonitor">
|
<Confirm ref="confirmPause" @yes="pauseMonitor">
|
||||||
@ -95,6 +102,7 @@ import Status from "../components/Status.vue";
|
|||||||
import Datetime from "../components/Datetime.vue";
|
import Datetime from "../components/Datetime.vue";
|
||||||
import CountUp from "../components/CountUp.vue";
|
import CountUp from "../components/CountUp.vue";
|
||||||
import Uptime from "../components/Uptime.vue";
|
import Uptime from "../components/Uptime.vue";
|
||||||
|
import Pagination from "v-pagination-3";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@ -104,13 +112,16 @@ export default {
|
|||||||
HeartbeatBar,
|
HeartbeatBar,
|
||||||
Confirm,
|
Confirm,
|
||||||
Status,
|
Status,
|
||||||
|
Pagination,
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
page: 1,
|
||||||
|
perPage: 25,
|
||||||
|
heartBeatList: [],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -154,6 +165,7 @@ export default {
|
|||||||
|
|
||||||
importantHeartBeatList() {
|
importantHeartBeatList() {
|
||||||
if (this.$root.importantHeartbeatList[this.monitor.id]) {
|
if (this.$root.importantHeartbeatList[this.monitor.id]) {
|
||||||
|
this.heartBeatList = this.$root.importantHeartbeatList[this.monitor.id];
|
||||||
return this.$root.importantHeartbeatList[this.monitor.id]
|
return this.$root.importantHeartbeatList[this.monitor.id]
|
||||||
} else {
|
} else {
|
||||||
return [];
|
return [];
|
||||||
@ -166,8 +178,13 @@ export default {
|
|||||||
} else {
|
} else {
|
||||||
return { }
|
return { }
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
|
||||||
|
displayedRecords() {
|
||||||
|
const startIndex = this.perPage * (this.page - 1);
|
||||||
|
const endIndex = startIndex + this.perPage;
|
||||||
|
return this.heartBeatList.slice(startIndex, endIndex);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
testNotification() {
|
testNotification() {
|
||||||
|
@ -124,7 +124,7 @@ export default {
|
|||||||
name: "",
|
name: "",
|
||||||
url: "https://",
|
url: "https://",
|
||||||
interval: 60,
|
interval: 60,
|
||||||
maxretries: 0,
|
maxretries: 0,
|
||||||
notificationIDList: {},
|
notificationIDList: {},
|
||||||
}
|
}
|
||||||
} else if (this.isEdit) {
|
} else if (this.isEdit) {
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
<label for="timezone" class="form-label">Timezone</label>
|
<label for="timezone" class="form-label">Timezone</label>
|
||||||
<select class="form-select" id="timezone" v-model="$root.userTimezone">
|
<select class="form-select" id="timezone" v-model="$root.userTimezone">
|
||||||
<option value="auto">Auto: {{ guessTimezone }}</option>
|
<option value="auto">Auto: {{ guessTimezone }}</option>
|
||||||
<option v-for="timezone in timezoneList" :value="timezone.value">{{ timezone.name }}</option>
|
<option v-for="(timezone, index) in timezoneList" :value="timezone.value" :key="index">{{ timezone.name }}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -59,7 +59,7 @@
|
|||||||
<p v-else>Please assign the notification to monitor(s) to get it works.</p>
|
<p v-else>Please assign the notification to monitor(s) to get it works.</p>
|
||||||
|
|
||||||
<ul class="list-group mb-3" style="border-radius: 1rem;">
|
<ul class="list-group mb-3" style="border-radius: 1rem;">
|
||||||
<li class="list-group-item" v-for="notification in $root.notificationList">
|
<li class="list-group-item" v-for="(notification, index) in $root.notificationList" :key="index">
|
||||||
{{ notification.name }}<br />
|
{{ notification.name }}<br />
|
||||||
<a href="#" @click="$refs.notificationDialog.show(notification.id)">Edit</a>
|
<a href="#" @click="$refs.notificationDialog.show(notification.id)">Edit</a>
|
||||||
</li>
|
</li>
|
||||||
@ -77,8 +77,8 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import utc from 'dayjs/plugin/utc'
|
import utc from 'dayjs/plugin/utc'
|
||||||
import timezone from 'dayjs/plugin/timezone'
|
import timezone from 'dayjs/plugin/timezone'
|
||||||
import NotificationDialog from "../components/NotificationDialog.vue";
|
import NotificationDialog from "../components/NotificationDialog.vue";
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
dayjs.extend(timezone)
|
dayjs.extend(timezone)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import utc from 'dayjs/plugin/utc'
|
import utc from 'dayjs/plugin/utc'
|
||||||
import timezone from 'dayjs/plugin/timezone'
|
import timezone from 'dayjs/plugin/timezone'
|
||||||
|
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
dayjs.extend(timezone)
|
dayjs.extend(timezone)
|
||||||
@ -29,6 +29,7 @@ function getTimezoneOffset(timeZone) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// From: https://stackoverflow.com/questions/38399465/how-to-get-list-of-all-timezones-in-javascript
|
// From: https://stackoverflow.com/questions/38399465/how-to-get-list-of-all-timezones-in-javascript
|
||||||
|
// TODO: Move to separate file
|
||||||
const aryIannaTimeZones = [
|
const aryIannaTimeZones = [
|
||||||
'Europe/Andorra',
|
'Europe/Andorra',
|
||||||
'Asia/Dubai',
|
'Asia/Dubai',
|
||||||
@ -412,4 +413,3 @@ export function timezoneList() {
|
|||||||
|
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user