From 13cf6891acf99e55fdb10bb769f651df5c19eb44 Mon Sep 17 00:00:00 2001 From: Andreas Brett Date: Sun, 10 Oct 2021 21:58:23 +0200 Subject: [PATCH 01/41] cryptographically strong secret generation generate TOTP secret using WebCrypto API (see https://github.com/louislam/uptime-kuma/issues/640) --- src/util.ts | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/util.ts b/src/util.ts index 6e911998..22279a7d 100644 --- a/src/util.ts +++ b/src/util.ts @@ -114,12 +114,21 @@ export function getRandomInt(min: number, max: number) { return Math.floor(Math.random() * (max - min + 1)) + min; } +export function getCryptoRandomInt(min, max) { + const randomBuffer = new Uint32Array(1); + crypto.getRandomValues(randomBuffer); + let randomNumber = randomBuffer[0] / (0xffffffff + 1); + min = Math.ceil(min); + max = Math.floor(max); + return Math.floor(randomNumber * (max - min + 1)) + min; +} + export function genSecret(length = 64) { let secret = ""; - let chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - let charsLength = chars.length; - for ( let i = 0; i < length; i++ ) { - secret += chars.charAt(Math.floor(Math.random() * charsLength)); + const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + const charsLength = chars.length; + for ( let i = 0; i < 64; i++ ) { + secret += chars.charAt(getCryptoRandomInt(0, charsLength)); } return secret; } From 075535ba460a9f20439748b72d6f48d88a79a8e8 Mon Sep 17 00:00:00 2001 From: Andreas Brett Date: Sun, 10 Oct 2021 21:59:23 +0200 Subject: [PATCH 02/41] Update util.ts --- src/util.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util.ts b/src/util.ts index 22279a7d..6fdfc3be 100644 --- a/src/util.ts +++ b/src/util.ts @@ -127,7 +127,7 @@ export function genSecret(length = 64) { let secret = ""; const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; const charsLength = chars.length; - for ( let i = 0; i < 64; i++ ) { + for ( let i = 0; i < length; i++ ) { secret += chars.charAt(getCryptoRandomInt(0, charsLength)); } return secret; From e127e168b6b6295b38dc32e722466a61f94fa4cb Mon Sep 17 00:00:00 2001 From: Andreas Brett Date: Sun, 10 Oct 2021 22:15:42 +0200 Subject: [PATCH 03/41] typed parameters --- src/util.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util.ts b/src/util.ts index 6fdfc3be..8ba53738 100644 --- a/src/util.ts +++ b/src/util.ts @@ -114,7 +114,7 @@ export function getRandomInt(min: number, max: number) { return Math.floor(Math.random() * (max - min + 1)) + min; } -export function getCryptoRandomInt(min, max) { +export function getCryptoRandomInt(min; number, max: number) { const randomBuffer = new Uint32Array(1); crypto.getRandomValues(randomBuffer); let randomNumber = randomBuffer[0] / (0xffffffff + 1); From 06310423f4483164b0e43df30eb8f92ec2ca61a9 Mon Sep 17 00:00:00 2001 From: Andreas Brett Date: Sun, 10 Oct 2021 22:19:10 +0200 Subject: [PATCH 04/41] typo --- src/util.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util.ts b/src/util.ts index 8ba53738..6f058eed 100644 --- a/src/util.ts +++ b/src/util.ts @@ -114,7 +114,7 @@ export function getRandomInt(min: number, max: number) { return Math.floor(Math.random() * (max - min + 1)) + min; } -export function getCryptoRandomInt(min; number, max: number) { +export function getCryptoRandomInt(min: number, max: number) { const randomBuffer = new Uint32Array(1); crypto.getRandomValues(randomBuffer); let randomNumber = randomBuffer[0] / (0xffffffff + 1); From 11bcd1e2ed06651b8cf07e8df76ef20e5a6bca6a Mon Sep 17 00:00:00 2001 From: Andreas Brett Date: Sun, 10 Oct 2021 22:55:32 +0200 Subject: [PATCH 05/41] const --- src/util.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util.ts b/src/util.ts index 6f058eed..205589fe 100644 --- a/src/util.ts +++ b/src/util.ts @@ -117,7 +117,7 @@ export function getRandomInt(min: number, max: number) { export function getCryptoRandomInt(min: number, max: number) { const randomBuffer = new Uint32Array(1); crypto.getRandomValues(randomBuffer); - let randomNumber = randomBuffer[0] / (0xffffffff + 1); + const randomNumber = randomBuffer[0] / (0xffffffff + 1); min = Math.ceil(min); max = Math.floor(max); return Math.floor(randomNumber * (max - min + 1)) + min; From 0e6d7694cebcc0ec752ebab701a895890a9363ef Mon Sep 17 00:00:00 2001 From: Andreas Brett Date: Sun, 10 Oct 2021 23:54:45 +0200 Subject: [PATCH 06/41] Update util.js --- src/util.js | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/util.js b/src/util.js index 7fb50c5b..265ed51d 100644 --- a/src/util.js +++ b/src/util.js @@ -102,12 +102,21 @@ function getRandomInt(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; } exports.getRandomInt = getRandomInt; +function getCryptoRandomInt(min, max) { + const randomBuffer = new Uint32Array(1); + crypto.getRandomValues(randomBuffer); + const randomNumber = randomBuffer[0] / (0xffffffff + 1); + min = Math.ceil(min); + max = Math.floor(max); + return Math.floor(randomNumber * (max - min + 1)) + min; +} +exports.getCryptoRandomInt = getCryptoRandomInt; function genSecret(length = 64) { let secret = ""; - let chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - let charsLength = chars.length; - for (let i = 0; i < length; i++) { - secret += chars.charAt(Math.floor(Math.random() * charsLength)); + const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + const charsLength = chars.length; + for ( let i = 0; i < length; i++ ) { + secret += chars.charAt(getCryptoRandomInt(0, charsLength)); } return secret; } From dc1de50a02d7f4ea3205f179b60495be23403692 Mon Sep 17 00:00:00 2001 From: Andreas Brett Date: Mon, 11 Oct 2021 01:18:33 +0200 Subject: [PATCH 07/41] fix for max-inclusive --- src/util.js | 254 ++++++++++++++++++++++++++-------------------------- src/util.ts | 2 +- 2 files changed, 128 insertions(+), 128 deletions(-) diff --git a/src/util.js b/src/util.js index 265ed51d..8135f2a0 100644 --- a/src/util.js +++ b/src/util.js @@ -1,127 +1,127 @@ -"use strict"; -// Common Util for frontend and backend -// -// DOT NOT MODIFY util.js! -// Need to run "tsc" to compile if there are any changes. -// -// Backend uses the compiled file util.js -// Frontend uses util.ts -Object.defineProperty(exports, "__esModule", { value: true }); -exports.getMonitorRelativeURL = exports.genSecret = exports.getRandomInt = exports.getRandomArbitrary = exports.TimeLogger = exports.polyfill = exports.debug = exports.ucfirst = exports.sleep = exports.flipStatus = exports.STATUS_PAGE_PARTIAL_DOWN = exports.STATUS_PAGE_ALL_UP = exports.STATUS_PAGE_ALL_DOWN = exports.PENDING = exports.UP = exports.DOWN = exports.appName = exports.isDev = void 0; -const _dayjs = require("dayjs"); -const dayjs = _dayjs; -exports.isDev = process.env.NODE_ENV === "development"; -exports.appName = "Uptime Kuma"; -exports.DOWN = 0; -exports.UP = 1; -exports.PENDING = 2; -exports.STATUS_PAGE_ALL_DOWN = 0; -exports.STATUS_PAGE_ALL_UP = 1; -exports.STATUS_PAGE_PARTIAL_DOWN = 2; -function flipStatus(s) { - if (s === exports.UP) { - return exports.DOWN; - } - if (s === exports.DOWN) { - return exports.UP; - } - return s; -} -exports.flipStatus = flipStatus; -function sleep(ms) { - return new Promise(resolve => setTimeout(resolve, ms)); -} -exports.sleep = sleep; -/** - * PHP's ucfirst - * @param str - */ -function ucfirst(str) { - if (!str) { - return str; - } - const firstLetter = str.substr(0, 1); - return firstLetter.toUpperCase() + str.substr(1); -} -exports.ucfirst = ucfirst; -function debug(msg) { - if (exports.isDev) { - console.log(msg); - } -} -exports.debug = debug; -function polyfill() { - /** - * String.prototype.replaceAll() polyfill - * https://gomakethings.com/how-to-replace-a-section-of-a-string-with-another-one-with-vanilla-js/ - * @author Chris Ferdinandi - * @license MIT - */ - if (!String.prototype.replaceAll) { - String.prototype.replaceAll = function (str, newStr) { - // If a regex pattern - if (Object.prototype.toString.call(str).toLowerCase() === "[object regexp]") { - return this.replace(str, newStr); - } - // If a string - return this.replace(new RegExp(str, "g"), newStr); - }; - } -} -exports.polyfill = polyfill; -class TimeLogger { - constructor() { - this.startTime = dayjs().valueOf(); - } - print(name) { - if (exports.isDev && process.env.TIMELOGGER === "1") { - console.log(name + ": " + (dayjs().valueOf() - this.startTime) + "ms"); - } - } -} -exports.TimeLogger = TimeLogger; -/** - * Returns a random number between min (inclusive) and max (exclusive) - */ -function getRandomArbitrary(min, max) { - return Math.random() * (max - min) + min; -} -exports.getRandomArbitrary = getRandomArbitrary; -/** - * From: https://stackoverflow.com/questions/1527803/generating-random-whole-numbers-in-javascript-in-a-specific-range - * - * Returns a random integer between min (inclusive) and max (inclusive). - * The value is no lower than min (or the next integer greater than min - * if min isn't an integer) and no greater than max (or the next integer - * lower than max if max isn't an integer). - * Using Math.round() will give you a non-uniform distribution! - */ -function getRandomInt(min, max) { - min = Math.ceil(min); - max = Math.floor(max); - return Math.floor(Math.random() * (max - min + 1)) + min; -} -exports.getRandomInt = getRandomInt; -function getCryptoRandomInt(min, max) { - const randomBuffer = new Uint32Array(1); - crypto.getRandomValues(randomBuffer); - const randomNumber = randomBuffer[0] / (0xffffffff + 1); - min = Math.ceil(min); - max = Math.floor(max); - return Math.floor(randomNumber * (max - min + 1)) + min; -} -exports.getCryptoRandomInt = getCryptoRandomInt; -function genSecret(length = 64) { - let secret = ""; - const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - const charsLength = chars.length; - for ( let i = 0; i < length; i++ ) { - secret += chars.charAt(getCryptoRandomInt(0, charsLength)); - } - return secret; -} -exports.genSecret = genSecret; -function getMonitorRelativeURL(id) { - return "/dashboard/" + id; -} -exports.getMonitorRelativeURL = getMonitorRelativeURL; +"use strict"; +// Common Util for frontend and backend +// +// DOT NOT MODIFY util.js! +// Need to run "tsc" to compile if there are any changes. +// +// Backend uses the compiled file util.js +// Frontend uses util.ts +Object.defineProperty(exports, "__esModule", { value: true }); +exports.getMonitorRelativeURL = exports.genSecret = exports.getRandomInt = exports.getRandomArbitrary = exports.TimeLogger = exports.polyfill = exports.debug = exports.ucfirst = exports.sleep = exports.flipStatus = exports.STATUS_PAGE_PARTIAL_DOWN = exports.STATUS_PAGE_ALL_UP = exports.STATUS_PAGE_ALL_DOWN = exports.PENDING = exports.UP = exports.DOWN = exports.appName = exports.isDev = void 0; +const _dayjs = require("dayjs"); +const dayjs = _dayjs; +exports.isDev = process.env.NODE_ENV === "development"; +exports.appName = "Uptime Kuma"; +exports.DOWN = 0; +exports.UP = 1; +exports.PENDING = 2; +exports.STATUS_PAGE_ALL_DOWN = 0; +exports.STATUS_PAGE_ALL_UP = 1; +exports.STATUS_PAGE_PARTIAL_DOWN = 2; +function flipStatus(s) { + if (s === exports.UP) { + return exports.DOWN; + } + if (s === exports.DOWN) { + return exports.UP; + } + return s; +} +exports.flipStatus = flipStatus; +function sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); +} +exports.sleep = sleep; +/** + * PHP's ucfirst + * @param str + */ +function ucfirst(str) { + if (!str) { + return str; + } + const firstLetter = str.substr(0, 1); + return firstLetter.toUpperCase() + str.substr(1); +} +exports.ucfirst = ucfirst; +function debug(msg) { + if (exports.isDev) { + console.log(msg); + } +} +exports.debug = debug; +function polyfill() { + /** + * String.prototype.replaceAll() polyfill + * https://gomakethings.com/how-to-replace-a-section-of-a-string-with-another-one-with-vanilla-js/ + * @author Chris Ferdinandi + * @license MIT + */ + if (!String.prototype.replaceAll) { + String.prototype.replaceAll = function (str, newStr) { + // If a regex pattern + if (Object.prototype.toString.call(str).toLowerCase() === "[object regexp]") { + return this.replace(str, newStr); + } + // If a string + return this.replace(new RegExp(str, "g"), newStr); + }; + } +} +exports.polyfill = polyfill; +class TimeLogger { + constructor() { + this.startTime = dayjs().valueOf(); + } + print(name) { + if (exports.isDev && process.env.TIMELOGGER === "1") { + console.log(name + ": " + (dayjs().valueOf() - this.startTime) + "ms"); + } + } +} +exports.TimeLogger = TimeLogger; +/** + * Returns a random number between min (inclusive) and max (exclusive) + */ +function getRandomArbitrary(min, max) { + return Math.random() * (max - min) + min; +} +exports.getRandomArbitrary = getRandomArbitrary; +/** + * From: https://stackoverflow.com/questions/1527803/generating-random-whole-numbers-in-javascript-in-a-specific-range + * + * Returns a random integer between min (inclusive) and max (inclusive). + * The value is no lower than min (or the next integer greater than min + * if min isn't an integer) and no greater than max (or the next integer + * lower than max if max isn't an integer). + * Using Math.round() will give you a non-uniform distribution! + */ +function getRandomInt(min, max) { + min = Math.ceil(min); + max = Math.floor(max); + return Math.floor(Math.random() * (max - min + 1)) + min; +} +exports.getRandomInt = getRandomInt; +function getCryptoRandomInt(min, max) { + const randomBuffer = new Uint32Array(1); + crypto.getRandomValues(randomBuffer); + const randomNumber = randomBuffer[0] / (0xffffffff + 1); + min = Math.ceil(min); + max = Math.floor(max); + return Math.floor(randomNumber * (max - min + 1)) + min; +} +exports.getCryptoRandomInt = getCryptoRandomInt; +function genSecret(length = 64) { + let secret = ""; + const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + const charsLength = chars.length; + for ( let i = 0; i < length; i++ ) { + secret += chars.charAt(getCryptoRandomInt(0, charsLength - 1)); + } + return secret; +} +exports.genSecret = genSecret; +function getMonitorRelativeURL(id) { + return "/dashboard/" + id; +} +exports.getMonitorRelativeURL = getMonitorRelativeURL; diff --git a/src/util.ts b/src/util.ts index 205589fe..a1f6f259 100644 --- a/src/util.ts +++ b/src/util.ts @@ -128,7 +128,7 @@ export function genSecret(length = 64) { const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; const charsLength = chars.length; for ( let i = 0; i < length; i++ ) { - secret += chars.charAt(getCryptoRandomInt(0, charsLength)); + secret += chars.charAt(getCryptoRandomInt(0, charsLength - 1)); } return secret; } From dc805cff975d16145fb56c7783f1665c1f8c1d11 Mon Sep 17 00:00:00 2001 From: Phuong Nguyen Minh <73861816+mrphuongbn@users.noreply.github.com> Date: Wed, 13 Oct 2021 13:36:09 +0700 Subject: [PATCH 08/41] Add files via upload --- src/languages/vi.js | 285 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 285 insertions(+) create mode 100644 src/languages/vi.js diff --git a/src/languages/vi.js b/src/languages/vi.js new file mode 100644 index 00000000..dab84c79 --- /dev/null +++ b/src/languages/vi.js @@ -0,0 +1,285 @@ +export default { + languageName: "Vietnamese", + checkEverySecond: "Kiểm tra mỗi {0} giây.", + retryCheckEverySecond: "Thử lại mỗi {0} giây.", + retriesDescription: "Số lần thử lại tối đa trước khi dịch vụ được đánh dấu là down và gửi thông báo.", + ignoreTLSError: "Bỏ qua lỗi TLS/SSL với các web HTTPS.", + upsideDownModeDescription: "Trạng thái đảo ngược, nếu dịch vụ có thể truy cập được nghĩa là DOWN.", + maxRedirectDescription: "Số lần chuyển hướng (redirect) tối đa. Đặt thành 0 để tắt chuyển hướng", + acceptedStatusCodesDescription: "Chọn mã code trạng thái được coi là phản hồi thành công.", + passwordNotMatchMsg: "Mật khẩu nhập lại không khớp.", + notificationDescription: "Vui lòng chỉ định một kênh thông báo.", + keywordDescription: "Từ khoá tìm kiếm phản hồi ở dạng html hoặc JSON, có phân biệt chữ HOA - thường", + pauseDashboardHome: "Tạm dừng", + deleteMonitorMsg: "Bạn chắc chắn muốn xóa monitor này chứ?", + deleteNotificationMsg: "Bạn có chắc chắn muốn xóa kênh thông báo này cho tất cả monitor?", + resoverserverDescription: "Cloudflare là máy chủ mặc định, bạn có thể thay đổi bất cứ lúc nào.", + rrtypeDescription: "Hãy chọn RR-Type mà bạn muốn giám sát", + pauseMonitorMsg: "Bạn chắc chắn muốn tạm dừng chứ?", + enableDefaultNotificationDescription: "Bật làm mặc định cho mọi monitor mới về sau. Bạn vẫn có thể tắt thông báo riêng cho từng monitor.", + clearEventsMsg: "Bạn chắc chắn muốn xoá TẤT CẢ sự kiện cho monitor này chứ?", + clearHeartbeatsMsg: "Bạn chắc chắn muốn xoá TẤT CẢ heartbeats cho monitor này chứ?", + confirmClearStatisticsMsg: "Bạn chắc chắn muốn xoá TẤT CẢ số liệu thống kê?", + importHandleDescription: "Chọn 'Skip existing' nếu bạn muốn bỏ qua mọi monitor và kênh thông báo trùng tên. 'Overwrite' sẽ ghi đè lên tất cả các monitor và kênh thông báo.", + confirmImportMsg: "Bạn có chắc chắn muốn khôi phục bản bản sao lưu này không?.", + twoFAVerifyLabel: "Vui lòng nhập mã token của bạn để xác minh rằng 2FA đang hoạt động", + tokenValidSettingsMsg: "Mã token hợp lệ! Bạn có thể lưu cài đặt 2FA bây giờ.", + confirmEnableTwoFAMsg: "Bạn chắc chắn muốn bật 2FA chứ?", + confirmDisableTwoFAMsg: "Bạn chắc chắn muốn tắt 2FA chứ?", + Settings: "Cài đặt", + Dashboard: "Dashboard", + "New Update": "Bản cập nhật mới", + Language: "Ngôn ngữ", + Appearance: "Giao diện", + Theme: "Theme", + General: "Chung", + Version: "Phiên bản", + "Check Update On GitHub": "Kiểm tra bản cập nhật mới trên GitHub", + List: "List", + Add: "Thêm", + "Add New Monitor": "Thêm mới Monitor", + "Quick Stats": "Thống kê nhanh", + Up: "Lên", + Down: "Xuống", + Pending: "Chờ xử lý", + Unknown: "Không xác định", + Pause: "Tạm dừng", + Name: "Tên", + Status: "Trạng thái", + DateTime: "Ngày tháng", + Message: "Tin nhắn", + "No important events": "Không có sự kiện quan trọng nào", + Resume: "Khôi phục", + Edit: "Sửa", + Delete: "Xoá", + Current: "Hiện tại", + Uptime: "Uptime", + "Cert Exp.": "Cert hết hạn", + days: "ngày", + day: "ngày", + "-day": "-ngày", + hour: "giờ", + "-hour": "-giờ", + Response: "Phản hồi", + Ping: "Ping", + "Monitor Type": "Kiểu monitor", + Keyword: "Từ khoá", + "Friendly Name": "Tên dễ hiểu", + URL: "URL", + Hostname: "Hostname", + Port: "Port", + "Heartbeat Interval": "Tần suất heartbeat", + Retries: "Thử lại", + "Heartbeat Retry Interval": "Tần suất thử lại của Heartbeat", + Advanced: "Nâng cao", + "Upside Down Mode": "Trạng thái đảo ngược", + "Max. Redirects": "Chuyển hướng tối đa", + "Accepted Status Codes": "Codes trạng thái chấp nhận", + Save: "Lưu", + Notifications: "Thông báo", + "Not available, please setup.": "Chưa sẵn sàng, hãy cài đặt.", + "Setup Notification": "Cài đặt thông báo", + Light: "Sáng", + Dark: "Tối", + Auto: "Tự động", + "Theme - Heartbeat Bar": "Theme - Heartbeat Bar", + Normal: "Bình thường", + Bottom: "Dưới", + None: "Không có", + Timezone: "Múi giờ", + "Search Engine Visibility": "Hiển thị với các công cụ tìm kiếm", + "Allow indexing": "Cho phép indexing", + "Discourage search engines from indexing site": "Ngăn chặn các công cụ tìm kiếm indexing trang", + "Change Password": "Thay đổi mật khẩu", + "Current Password": "Mật khẩu hiện tại", + "New Password": "Mật khẩu mới", + "Repeat New Password": "Lặp lại mật khẩu mới", + "Update Password": "Cập nhật mật khẩu", + "Disable Auth": "Tắt xác minh", + "Enable Auth": "Bật xác minh", + Logout: "Đăng xuất", + Leave: "Rời", + "I understand, please disable": "Tôi hiểu, làm ơn hãy tắt!", + Confirm: "Xác nhận", + Yes: "Có", + No: "Không", + Username: "Tài khoản", + Password: "Mật khẩu", + "Remember me": "Lưu phiên đăng nhập", + Login: "Đăng nhập", + "No Monitors, please": "Không có monitor nào", + "add one": "Thêm mới", + "Notification Type": "Kiểu thông báo", + Email: "Email", + Test: "Thử", + "Certificate Info": "Thông tin Certificate", + "Resolver Server": "Máy chủ Resolver", + "Resource Record Type": "Loại bản ghi", + "Last Result": "Kết quả cuối cùng", + "Create your admin account": "Tạo tài khoản quản trị", + "Repeat Password": "Lặp lại mật khẩu", + "Import Backup": "Khôi phục bản sao lưu", + "Export Backup": "Xuất bản sao lưu", + Export: "Xuất", + Import: "Khôi phục", + respTime: "Thời gian phản hồi (ms)", + notAvailableShort: "N/A", + "Default enabled": "Mặc định bật", + "Apply on all existing monitors": "Áp dụng cho tất cả monitor đang có", + Create: "Tạo", + "Clear Data": "Xoá dữ liệu", + Events: "Sự kiện", + Heartbeats: "Heartbeats", + "Auto Get": "Tự động lấy", + backupDescription: "Bạn có thể sao lưu tất cả các màn hình và tất cả các thông báo vào một file JSON.", + backupDescription2: "PS: Không bao gồm dữ liệu lịch sử các sự kiện.", + backupDescription3: "Hãy lưu giữ file này cẩn thận vì trong file đó chứa cả các mã token thông báo.", + alertNoFile: "Hãy chọn file để khôi phục.", + alertWrongFileType: "Hãy chọn file JSON.", + "Clear all statistics": "Xoá tất cả thống kê", + "Skip existing": "Skip existing", + Overwrite: "Ghi đè", + Options: "Tuỳ chọn", + "Keep both": "Giữ lại cả hai", + "Verify Token": "Xác minh Token", + "Setup 2FA": "Cài đặt 2FA", + "Enable 2FA": "Bật 2FA", + "Disable 2FA": "Tắt 2FA", + "2FA Settings": "Cài đặt 2FA", + "Two Factor Authentication": "Xác thực hai yếu tố", + Active: "Hoạt động", + Inactive: "Ngừng hoạt động", + Token: "Token", + "Show URI": "Hiển thị URI", + Tags: "Tags", + "Add New below or Select...": "Thêm mới ở dưới hoặc Chọn...", + "Tag with this name already exist.": "Tag với tên đã tồn tại.", + "Tag with this value already exist.": "Tag với value đã tồn tại.", + color: "Màu sắc", + "value (optional)": "Value (tuỳ chọn)", + Gray: "Xám", + Red: "Đỏ", + Orange: "Cam", + Green: "Xanh lá", + Blue: "Xanh da trời", + Indigo: "Chàm", + Purple: "Tím", + Pink: "Hồng", + "Search...": "Tìm kiếm...", + "Avg. Ping": "Ping Trung bình", + "Avg. Response": "Phản hồi trung bình", + "Entry Page": "Entry Page", + statusPageNothing: "Không có gì, hãy thêm nhóm monitor hoặc monitor.", + "No Services": "Không có dịch vụ", + "All Systems Operational": "Tất cả các hệ thống hoạt động", + "Partially Degraded Service": "Dịch vụ xuống cấp một phần", + "Degraded Service": "Degraded Service", + "Add Group": "Thêm nhóm", + "Add a monitor": "Thêm monitor", + "Edit Status Page": "Sửa trang trạng thái", + "Go to Dashboard": "Đi tới Dashboard", + "Status Page": "Trang trạng thái", + // Start notification form + defaultNotificationName: "My {notification} Alerts ({number})", + here: "tại đây", + "Required": "Bắt buộc", + "telegram": "Telegram", + "Bot Token": "Bot Token", + "You can get a token from": "Bạn có thể lấy mã token từ", + "Chat ID": "Chat ID", + supportTelegramChatID: "Hỗ trợ chat trực tiếp / Nhóm / Kênh Chat ID", + wayToGetTelegramChatID: "Bạn có thể lấy chat id của mình bằng cách gửi tin nhắn tới bot và truy cập url này để xem chat_id:", + "YOUR BOT TOKEN HERE": "MÃ BOT TOKEN CỦA BẠN", + chatIDNotFound: "Không tìm thấy Chat ID, vui lòng gửi tin nhắn cho bot này trước", + "webhook": "Webhook", + "Post URL": "URL đăng", + "Content Type": "Loại nội dung", + webhookJsonDesc: "{0} phù hợp với bất kỳ máy chủ http hiện đại nào như express.js", + webhookFormDataDesc: "{multipart} phù hợp với PHP, bạn chỉ cần phân tích cú pháp json bằng {decodeFunction}", + "smtp": "Email (SMTP)", + secureOptionNone: "None / STARTTLS (25, 587)", + secureOptionTLS: "TLS (465)", + "Ignore TLS Error": "Bỏ qua lỗi TLS", + "From Email": "Từ Email", + "To Email": "Tới Email", + smtpCC: "CC", + smtpBCC: "BCC", + "discord": "Discord", + "Discord Webhook URL": "Discord Webhook URL", + wayToGetDiscordURL: "Để lấy Discord, hãy vào: Server Settings -> Integrations -> Create Webhook", + "Bot Display Name": "Tên hiển thị của Bot", + "Prefix Custom Message": "Tiền tố tin nhắn tuỳ chọn", + "Hello @everyone is...": "Xin chào {'@'} mọi người đang...", + "teams": "Microsoft Teams", + "Webhook URL": "Webhook URL", + wayToGetTeamsURL: "Bạn có thể học cách tạo webhook url {0}.", + "signal": "Signal", + "Number": "Số", + "Recipients": "Người nhận", + needSignalAPI: "Bạn cần một tín hiệu client với REST API.", + wayToCheckSignalURL: "Bạn có thể kiểm tra url này để xem cách thiết lập:", + signalImportant: "QUAN TRỌNG: Bạn không thể kết hợp các nhóm và số trong người nhận!", + "gotify": "Gotify", + "Application Token": "Mã Token ứng dụng", + "Server URL": "URL máy chủ", + "Priority": "Mức ưu tiên", + "slack": "Slack", + "Icon Emoji": "Icon Emoji", + "Channel Name": "Tên Channel", + "Uptime Kuma URL": "Uptime Kuma URL", + aboutWebhooks: "Thông tin thêm về webhook trên: {0}", + aboutChannelName: "Nhập tên kênh trên {0} trường Channel Name nếu bạn muốn bỏ qua kênh webhook. vd: #other-channel", + aboutKumaURL: "Nếu bạn để trống trường Uptime Kuma URL, mặc định sẽ là trang Project Github.", + emojiCheatSheet: "Bảng tra cứu Emoji: {0}", + "rocket.chat": "Rocket.chat", + pushover: "Pushover", + pushy: "Pushy", + octopush: "Octopush", + promosms: "PromoSMS", + lunasea: "LunaSea", + apprise: "Thông báo (Hỗ trợ 50+ dịch vụ thông báo)", + pushbullet: "Pushbullet", + line: "Line Messenger", + mattermost: "Mattermost", + "User Key": "User Key", + "Device": "Thiết bị", + "Message Title": "Tiêu đề tin nhắn", + "Notification Sound": "Âm thanh thông báo", + "More info on:": "Thông tin chi tiết tại: {0}", + pushoverDesc1: "Mức ưu tiên khẩn cấp (2) có thời gian chờ mặc định là 30 giây giữa các lần thử lại và sẽ hết hạn sau 1 giờ.", + pushoverDesc2: "Nếu bạn muốn gửi thông báo đến các thiết bị khác nhau, hãy điền vào trường Thiết bị.", + "SMS Type": "SMS Type", + octopushTypePremium: "Premium (Nhanh - Khuyến nghị nên dùng cho cảnh báo)", + octopushTypeLowCost: "Giá rẻ (Chậm, thỉnh thoảng bị chặn)", + "Check octopush prices": "Kiểm tra giá octopush {0}.", + octopushPhoneNumber: "Số điện thoại (Định dạng intl, vd : +33612345678) ", + octopushSMSSender: "SMS người gửi : 3-11 ký tự chữ, số và dấu cách (a-zA-Z0-9)", + "LunaSea Device ID": "LunaSea ID thiết bị", + "Apprise URL": "URL thông báo", + "Example:": "Ví dụ: {0}", + "Read more:": "Đọc thêm: {0}", + "Status:": "Trạng thái: {0}", + "Read more": "Đọc thêm", + appriseInstalled: "Đã cài đặt Thông báo.", + appriseNotInstalled: "Chưa cài đặt Thông báo. {0}", + "Access Token": "Token truy cập", + "Channel access token": "Token kênh truy cập", + "Line Developers Console": "Line Developers Console", + lineDevConsoleTo: "Line Developers Console - {0}", + "Basic Settings": "Cài đặt cơ bản", + "User ID": "User ID", + "Messaging API": "Messaging API", + wayToGetLineChannelToken: "Trước tiên, hãy truy cập {0},tạo nhà cung cấp và kênh (Messaging API), sau đó bạn có thể nhận mã token truy cập kênh và id người dùng từ các mục menu được đề cập ở trên.", + "Icon URL": "Icon URL", + aboutIconURL: "Bạn có thể cung cấp liên kết đến ảnh trong \"Icon URL\" để ghi đè ảnh hồ sơ mặc định. Sẽ không được sử dụng nếu Biểu tượng cảm xúc được thiết lập.", + aboutMattermostChannelName: "Bạn có thể ghi đè kênh mặc định mà webhook đăng lên bằng cách nhập tên kênh vào trường \"Channel Name\". Điều này cần được bật trong cài đặt Mattermost webhook. Ví dụ: #other-channel", + "matrix": "Matrix", + promosmsTypeEco: "SMS ECO - rẻ nhưng chậm và thường xuyên quá tải. Chỉ dành cho người Ba Lan.", + promosmsTypeFlash: "SMS FLASH - Tin nhắn sẽ tự động hiển thị trên thiết bị của người nhận. Chỉ dành cho người Ba Lan.", + promosmsTypeFull: "SMS FULL - SMS cao cấp, Bạn có thể sử dụng Tên Người gửi (Bạn cần đăng ký tên trước). Đáng tin cậy cho các cảnh báo.", + promosmsTypeSpeed: "SMS SPEED - Ưu tiên cao nhất trong hệ thống. Rất nhanh chóng và đáng tin cậy nhưng tốn kém, (giá gấp đôi SMS FULL).", + promosmsPhoneNumber: "Số điện thoại (Bỏ qua mã vùng với người Ba Lan)", + promosmsSMSSender: "SMS Tên người gửi: Tên đã đăng ký trước hoặc tên mặc định: InfoSMS, SMS Info, MaxSMS, INFO, SMS", + "Feishu WebHookUrl": "Feishu WebHookUrl", + // End notification form +}; \ No newline at end of file From 89c0f8b7349d173ba262709ef8138cf3be057009 Mon Sep 17 00:00:00 2001 From: Phuong Nguyen Minh <73861816+mrphuongbn@users.noreply.github.com> Date: Wed, 13 Oct 2021 13:44:31 +0700 Subject: [PATCH 09/41] Update i18n.js --- src/i18n.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/i18n.js b/src/i18n.js index b95e32d0..7f55a061 100644 --- a/src/i18n.js +++ b/src/i18n.js @@ -21,9 +21,11 @@ import sr from "./languages/sr"; import srLatn from "./languages/sr-latn"; import svSE from "./languages/sv-SE"; import trTR from "./languages/tr-TR"; +import vi from "./languages/vi"; import zhCN from "./languages/zh-CN"; import zhHK from "./languages/zh-HK"; + const languageList = { en, "zh-HK": zhHK, @@ -49,6 +51,7 @@ const languageList = { "zh-CN": zhCN, "pl": pl, "et-EE": etEE, + "vi": vi, }; const rtlLangs = ["fa"]; From 86dcc9bc8fc39faa44949c52ad74d9831dfedfb4 Mon Sep 17 00:00:00 2001 From: Andreas Brett Date: Wed, 13 Oct 2021 17:34:56 +0200 Subject: [PATCH 10/41] import webcrypto lib --- src/util.js | 1 + src/util.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/src/util.js b/src/util.js index 8135f2a0..b8ee76d9 100644 --- a/src/util.js +++ b/src/util.js @@ -10,6 +10,7 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.getMonitorRelativeURL = exports.genSecret = exports.getRandomInt = exports.getRandomArbitrary = exports.TimeLogger = exports.polyfill = exports.debug = exports.ucfirst = exports.sleep = exports.flipStatus = exports.STATUS_PAGE_PARTIAL_DOWN = exports.STATUS_PAGE_ALL_UP = exports.STATUS_PAGE_ALL_DOWN = exports.PENDING = exports.UP = exports.DOWN = exports.appName = exports.isDev = void 0; const _dayjs = require("dayjs"); const dayjs = _dayjs; +const crypto = require("crypto").webcrypto; exports.isDev = process.env.NODE_ENV === "development"; exports.appName = "Uptime Kuma"; exports.DOWN = 0; diff --git a/src/util.ts b/src/util.ts index a1f6f259..259ff6e5 100644 --- a/src/util.ts +++ b/src/util.ts @@ -8,6 +8,7 @@ import * as _dayjs from "dayjs"; const dayjs = _dayjs; +const crypto = require("crypto").webcrypto; export const isDev = process.env.NODE_ENV === "development"; export const appName = "Uptime Kuma"; From bda481c61eb603a8a7fe5bfd980ea129804978a5 Mon Sep 17 00:00:00 2001 From: Phuong Nguyen Minh <73861816+mrphuongbn@users.noreply.github.com> Date: Fri, 15 Oct 2021 08:26:33 +0700 Subject: [PATCH 11/41] Update vi.js --- src/languages/vi.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/languages/vi.js b/src/languages/vi.js index dab84c79..c47ebe02 100644 --- a/src/languages/vi.js +++ b/src/languages/vi.js @@ -255,13 +255,13 @@ export default { octopushPhoneNumber: "Số điện thoại (Định dạng intl, vd : +33612345678) ", octopushSMSSender: "SMS người gửi : 3-11 ký tự chữ, số và dấu cách (a-zA-Z0-9)", "LunaSea Device ID": "LunaSea ID thiết bị", - "Apprise URL": "URL thông báo", + "Apprise URL": "Apprise URL", "Example:": "Ví dụ: {0}", "Read more:": "Đọc thêm: {0}", "Status:": "Trạng thái: {0}", "Read more": "Đọc thêm", - appriseInstalled: "Đã cài đặt Thông báo.", - appriseNotInstalled: "Chưa cài đặt Thông báo. {0}", + appriseInstalled: "Đã cài đặt Apprise.", + appriseNotInstalled: "Chưa cài đặt Apprise. {0}", "Access Token": "Token truy cập", "Channel access token": "Token kênh truy cập", "Line Developers Console": "Line Developers Console", @@ -282,4 +282,4 @@ export default { promosmsSMSSender: "SMS Tên người gửi: Tên đã đăng ký trước hoặc tên mặc định: InfoSMS, SMS Info, MaxSMS, INFO, SMS", "Feishu WebHookUrl": "Feishu WebHookUrl", // End notification form -}; \ No newline at end of file +}; From 8cb26d2b31c0984a34a35cb35dd01b94b02c5674 Mon Sep 17 00:00:00 2001 From: NeuralMiner Date: Fri, 15 Oct 2021 19:12:09 -0600 Subject: [PATCH 12/41] Text update --- server/database.js | 20 ++++++++++---------- server/server.js | 13 ++++++++----- src/pages/EditMonitor.vue | 2 +- src/pages/Setup.vue | 2 +- 4 files changed, 20 insertions(+), 17 deletions(-) diff --git a/server/database.js b/server/database.js index 297df655..4c7e5364 100644 --- a/server/database.js +++ b/server/database.js @@ -131,7 +131,7 @@ class Database { console.info("Latest database version: " + this.latestVersion); if (version === this.latestVersion) { - console.info("Database no need to patch"); + console.info("Database patch not needed"); } else if (version > this.latestVersion) { console.info("Warning: Database version is newer than expected"); } else { @@ -152,8 +152,8 @@ class Database { await Database.close(); 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"); + console.error("Start Uptime-Kuma failed due to issue patching the database"); + console.error("Please submit a bug report if you still encounter the problem after restart: https://github.com/louislam/uptime-kuma/issues"); this.restore(); process.exit(1); @@ -191,7 +191,7 @@ class Database { await Database.close(); console.error(ex); - console.error("Start Uptime-Kuma failed due to patch db failed"); + console.error("Start Uptime-Kuma failed due to issue patching the database"); console.error("Please submit the bug report if you still encounter the problem after restart: https://github.com/louislam/uptime-kuma/issues"); this.restore(); @@ -232,7 +232,7 @@ class Database { this.patched = true; await this.importSQLFile("./db/" + sqlFilename); databasePatchedFiles[sqlFilename] = true; - console.log(sqlFilename + " is patched successfully"); + console.log(sqlFilename + " was patched successfully"); } else { debug(sqlFilename + " is already patched, skip"); @@ -287,7 +287,7 @@ class Database { }; process.addListener("unhandledRejection", listener); - console.log("Closing DB"); + console.log("Closing the database"); while (true) { Database.noReject = true; @@ -297,7 +297,7 @@ class Database { if (Database.noReject) { break; } else { - console.log("Waiting to close the db"); + console.log("Waiting to close the database"); } } console.log("SQLite closed"); @@ -312,7 +312,7 @@ class Database { */ static backup(version) { if (! this.backupPath) { - console.info("Backup the db"); + console.info("Backup the database"); this.backupPath = this.dataDir + "kuma.db.bak" + version; fs.copyFileSync(Database.path, this.backupPath); @@ -335,7 +335,7 @@ class Database { */ static restore() { if (this.backupPath) { - console.error("Patch db failed!!! Restoring the backup"); + console.error("Patching the database failed!!! Restoring the backup"); const shmPath = Database.path + "-shm"; const walPath = Database.path + "-wal"; @@ -354,7 +354,7 @@ class Database { fs.unlinkSync(walPath); } } catch (e) { - console.log("Restore failed, you may need to restore the backup manually"); + console.log("Restore failed; you may need to restore the backup manually"); process.exit(1); } diff --git a/server/server.js b/server/server.js index 8d994d3b..329484f5 100644 --- a/server/server.js +++ b/server/server.js @@ -1,7 +1,6 @@ console.log("Welcome to Uptime Kuma"); const args = require("args-parser")(process.argv); const { sleep, debug, getRandomInt, genSecret } = require("../src/util"); -const config = require("./config"); debug(args); @@ -9,6 +8,10 @@ if (! process.env.NODE_ENV) { process.env.NODE_ENV = "production"; } +// Demo Mode? +const demoMode = args["demo"] || false; +exports.demoMode = demoMode; + console.log("Node Env: " + process.env.NODE_ENV); console.log("Importing Node libraries"); @@ -81,7 +84,7 @@ const sslCert = process.env.UPTIME_KUMA_SSL_CERT || process.env.SSL_CERT || args */ const testMode = !!args["test"] || false; -if (config.demoMode) { +if (demoMode) { console.log("==== Demo Mode ===="); } @@ -440,7 +443,7 @@ exports.entryPage = "dashboard"; socket.on("setup", async (username, password, callback) => { try { if ((await R.count("user")) !== 0) { - throw new Error("Uptime Kuma has been setup. If you want to setup again, please delete the database."); + throw new Error("Uptime Kuma has been set up. If you want to setup again, please delete the database."); } let user = R.dispense("user"); @@ -1328,7 +1331,7 @@ async function initDatabase() { fs.copyFileSync(Database.templatePath, Database.path); } - console.log("Connecting to Database"); + console.log("Connecting to the Database"); await Database.connect(); console.log("Connected"); @@ -1428,7 +1431,7 @@ async function shutdownFunction(signal) { } function finalFunction() { - console.log("Graceful shutdown successfully!"); + console.log("Graceful shutdown successful!"); } gracefulShutdown(server, { diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue index b2349021..06258256 100644 --- a/src/pages/EditMonitor.vue +++ b/src/pages/EditMonitor.vue @@ -467,7 +467,7 @@ export default { return; } - // Beautiful the JSON format + // Beautify the JSON format if (this.monitor.body) { this.monitor.body = JSON.stringify(JSON.parse(this.monitor.body), null, 4); } diff --git a/src/pages/Setup.vue b/src/pages/Setup.vue index 5a4f836f..ed8ea3ef 100644 --- a/src/pages/Setup.vue +++ b/src/pages/Setup.vue @@ -75,7 +75,7 @@ export default { this.processing = true; if (this.password !== this.repeatPassword) { - toast.error("Repeat password do not match."); + toast.error("Passwords do not match."); this.processing = false; return; } From c622f7958fe5ee42240de415466916beb0375f93 Mon Sep 17 00:00:00 2001 From: Juan Calderon-Perez <835733+gaby@users.noreply.github.com> Date: Sun, 17 Oct 2021 17:16:07 -0400 Subject: [PATCH 13/41] Add support for closing stale Issues/PR --- .github/workflows/stale-bot | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .github/workflows/stale-bot diff --git a/.github/workflows/stale-bot b/.github/workflows/stale-bot new file mode 100644 index 00000000..2d0b82cb --- /dev/null +++ b/.github/workflows/stale-bot @@ -0,0 +1,17 @@ +name: 'Automatically close stale issues and PRs' +on: + schedule: + - cron: '0 0 * * *' +#Run once a day at midnight + +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@v3 + with: + stale-issue-message: 'We are clearing up our old issues and your ticket has been open for 6 months with no activity. Remove stale label or comment or this will be closed in 15 days.' + days-before-stale: 180 + days-before-close: 15 + exempt-issue-labels: 'News,Medium,High,discussion,bug,doc,' + operations-per-run: 500 From 7cb25255bfddce6bed333fcbdafda2e21a38dfb3 Mon Sep 17 00:00:00 2001 From: Juan Calderon-Perez <835733+gaby@users.noreply.github.com> Date: Sun, 17 Oct 2021 17:20:32 -0400 Subject: [PATCH 14/41] Update stale-bot --- .github/workflows/stale-bot | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/stale-bot b/.github/workflows/stale-bot index 2d0b82cb..6dbcbb23 100644 --- a/.github/workflows/stale-bot +++ b/.github/workflows/stale-bot @@ -10,8 +10,14 @@ jobs: steps: - uses: actions/stale@v3 with: - stale-issue-message: 'We are clearing up our old issues and your ticket has been open for 6 months with no activity. Remove stale label or comment or this will be closed in 15 days.' + stale-issue-message: 'We are clearing up our old issues and your ticket has been open for 6 months with no activity. Remove stale label or comment or this will be closed in 7 days.' + stale-pr-message: ''We are clearing up our old Pull Requests and yours has been open for 6 months with no activity. Remove stale label or comment or this will be closed in 7 days.' + close-issue-message: 'This issue was closed because it has been stalled for 7 days with no activity.' + close-pr-message: 'This PR was closed because it has been stalled for 7 days with no activity.' days-before-stale: 180 - days-before-close: 15 + days-before-close: 7 exempt-issue-labels: 'News,Medium,High,discussion,bug,doc,' + exempt-pr-labels: 'awaiting-approval,work-in-progress,enhancement,' operations-per-run: 500 + exempt-issue-assignees: 'louislam' + exempt-pr-assignees: 'louislam' From 2a3ce15328fb1cccef04d9a7d7d10a4fb50dd04f Mon Sep 17 00:00:00 2001 From: Juan Calderon-Perez <835733+gaby@users.noreply.github.com> Date: Sun, 17 Oct 2021 17:23:44 -0400 Subject: [PATCH 15/41] Use default number of operations per day. Defaults to 30 --- .github/workflows/stale-bot | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/stale-bot b/.github/workflows/stale-bot index 6dbcbb23..5992b93e 100644 --- a/.github/workflows/stale-bot +++ b/.github/workflows/stale-bot @@ -18,6 +18,5 @@ jobs: days-before-close: 7 exempt-issue-labels: 'News,Medium,High,discussion,bug,doc,' exempt-pr-labels: 'awaiting-approval,work-in-progress,enhancement,' - operations-per-run: 500 exempt-issue-assignees: 'louislam' exempt-pr-assignees: 'louislam' From 11a1f35cc5cc1743de60b2bc4fba33a0109686eb Mon Sep 17 00:00:00 2001 From: Andreas Brett Date: Mon, 18 Oct 2021 01:06:20 +0200 Subject: [PATCH 16/41] independent csprng solution --- src/util.js | 87 +++++++++++++++++++++++++++++++++++++++-------------- src/util.ts | 66 +++++++++++++++++++++++++++++++++++----- 2 files changed, 122 insertions(+), 31 deletions(-) diff --git a/src/util.js b/src/util.js index b8ee76d9..df54cf2e 100644 --- a/src/util.js +++ b/src/util.js @@ -6,11 +6,10 @@ // // Backend uses the compiled file util.js // Frontend uses util.ts -Object.defineProperty(exports, "__esModule", { value: true }); -exports.getMonitorRelativeURL = exports.genSecret = exports.getRandomInt = exports.getRandomArbitrary = exports.TimeLogger = exports.polyfill = exports.debug = exports.ucfirst = exports.sleep = exports.flipStatus = exports.STATUS_PAGE_PARTIAL_DOWN = exports.STATUS_PAGE_ALL_UP = exports.STATUS_PAGE_ALL_DOWN = exports.PENDING = exports.UP = exports.DOWN = exports.appName = exports.isDev = void 0; -const _dayjs = require("dayjs"); -const dayjs = _dayjs; -const crypto = require("crypto").webcrypto; +exports.__esModule = true; +exports.getMonitorRelativeURL = exports.genSecret = exports.getCryptoRandomInt = exports.getRandomInt = exports.getRandomArbitrary = exports.TimeLogger = exports.polyfill = exports.debug = exports.ucfirst = exports.sleep = exports.flipStatus = exports.STATUS_PAGE_PARTIAL_DOWN = exports.STATUS_PAGE_ALL_UP = exports.STATUS_PAGE_ALL_DOWN = exports.PENDING = exports.UP = exports.DOWN = exports.appName = exports.isDev = void 0; +var _dayjs = require("dayjs"); +var dayjs = _dayjs; exports.isDev = process.env.NODE_ENV === "development"; exports.appName = "Uptime Kuma"; exports.DOWN = 0; @@ -30,7 +29,7 @@ function flipStatus(s) { } exports.flipStatus = flipStatus; function sleep(ms) { - return new Promise(resolve => setTimeout(resolve, ms)); + return new Promise(function (resolve) { return setTimeout(resolve, ms); }); } exports.sleep = sleep; /** @@ -41,7 +40,7 @@ function ucfirst(str) { if (!str) { return str; } - const firstLetter = str.substr(0, 1); + var firstLetter = str.substr(0, 1); return firstLetter.toUpperCase() + str.substr(1); } exports.ucfirst = ucfirst; @@ -70,16 +69,17 @@ function polyfill() { } } exports.polyfill = polyfill; -class TimeLogger { - constructor() { +var TimeLogger = /** @class */ (function () { + function TimeLogger() { this.startTime = dayjs().valueOf(); } - print(name) { + TimeLogger.prototype.print = function (name) { if (exports.isDev && process.env.TIMELOGGER === "1") { console.log(name + ": " + (dayjs().valueOf() - this.startTime) + "ms"); } - } -} + }; + return TimeLogger; +}()); exports.TimeLogger = TimeLogger; /** * Returns a random number between min (inclusive) and max (exclusive) @@ -103,20 +103,61 @@ function getRandomInt(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; } exports.getRandomInt = getRandomInt; +/** + * Returns either the NodeJS crypto.randomBytes() function or its + * browser equivalent implemented via window.crypto.getRandomValues() + */ +var getRandomBytes = ((typeof window !== 'undefined' && window.crypto) + // Browsers + ? function () { + return function (numBytes) { + var randomBytes = new Uint8Array(numBytes); + for (var i = 0; i < numBytes; i += 65536) { + window.crypto.getRandomValues(randomBytes.subarray(i, i + Math.min(numBytes - i, 65536))); + } + return randomBytes; + }; + } + // Node + : function () { + return require("crypto").randomBytes; + })(); function getCryptoRandomInt(min, max) { - const randomBuffer = new Uint32Array(1); - crypto.getRandomValues(randomBuffer); - const randomNumber = randomBuffer[0] / (0xffffffff + 1); - min = Math.ceil(min); - max = Math.floor(max); - return Math.floor(randomNumber * (max - min + 1)) + min; + // synchronous version of: https://github.com/joepie91/node-random-number-csprng + var range = max - min; + if (range >= Math.pow(2, 32)) + console.log("Warning! Range is too large."); + var tmpRange = range; + var bitsNeeded = 0; + var bytesNeeded = 0; + var mask = 1; + while (tmpRange > 0) { + if (bitsNeeded % 8 === 0) + bytesNeeded += 1; + bitsNeeded += 1; + mask = mask << 1 | 1; + tmpRange = tmpRange >>> 1; + } + var randomBytes = getRandomBytes(bytesNeeded); + var randomValue = 0; + for (var i = 0; i < bytesNeeded; i++) { + randomValue |= randomBytes[i] << 8 * i; + } + randomValue = randomValue & mask; + if (randomValue <= range) { + return min + randomValue; + } + else { + return getCryptoRandomInt(min, max); + } } exports.getCryptoRandomInt = getCryptoRandomInt; -function genSecret(length = 64) { - let secret = ""; - const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - const charsLength = chars.length; - for ( let i = 0; i < length; i++ ) { +function genSecret(length) { + if (length === void 0) { length = 64; } + var secret = ""; + var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + var charsLength = chars.length; + for (var i = 0; i < length; i++) { secret += chars.charAt(getCryptoRandomInt(0, charsLength - 1)); } return secret; diff --git a/src/util.ts b/src/util.ts index 259ff6e5..633d933e 100644 --- a/src/util.ts +++ b/src/util.ts @@ -8,7 +8,6 @@ import * as _dayjs from "dayjs"; const dayjs = _dayjs; -const crypto = require("crypto").webcrypto; export const isDev = process.env.NODE_ENV === "development"; export const appName = "Uptime Kuma"; @@ -115,13 +114,64 @@ export function getRandomInt(min: number, max: number) { return Math.floor(Math.random() * (max - min + 1)) + min; } -export function getCryptoRandomInt(min: number, max: number) { - const randomBuffer = new Uint32Array(1); - crypto.getRandomValues(randomBuffer); - const randomNumber = randomBuffer[0] / (0xffffffff + 1); - min = Math.ceil(min); - max = Math.floor(max); - return Math.floor(randomNumber * (max - min + 1)) + min; +/** + * Returns either the NodeJS crypto.randomBytes() function or its + * browser equivalent implemented via window.crypto.getRandomValues() + */ +let getRandomBytes = ( + (typeof window !== 'undefined' && window.crypto) + + // Browsers + ? function () { + return (numBytes: number) => { + let randomBytes = new Uint8Array(numBytes); + for (let i = 0; i < numBytes; i += 65536) { + window.crypto.getRandomValues(randomBytes.subarray(i, i + Math.min(numBytes - i, 65536))); + } + return randomBytes; + }; + } + + // Node + : function() { + return require("crypto").randomBytes; + } +)(); + +export function getCryptoRandomInt(min: number, max: number):number { + + // synchronous version of: https://github.com/joepie91/node-random-number-csprng + + const range = max - min + if (range >= Math.pow(2, 32)) + console.log("Warning! Range is too large.") + + let tmpRange = range + let bitsNeeded = 0 + let bytesNeeded = 0 + let mask = 1 + + while (tmpRange > 0) { + if (bitsNeeded % 8 === 0) bytesNeeded += 1 + bitsNeeded += 1 + mask = mask << 1 | 1 + tmpRange = tmpRange >>> 1 + } + + const randomBytes = getRandomBytes(bytesNeeded) + let randomValue = 0 + + for (let i = 0; i < bytesNeeded; i++) { + randomValue |= randomBytes[i] << 8 * i + } + + randomValue = randomValue & mask; + + if (randomValue <= range) { + return min + randomValue + } else { + return getCryptoRandomInt(min, max) + } } export function genSecret(length = 64) { From 23714ab68833cf110947ee2e32481d7f2fc5b906 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Mon, 18 Oct 2021 17:37:11 +0800 Subject: [PATCH 17/41] genSecret don't need `await` --- server/server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/server.js b/server/server.js index c4d18869..31f27043 100644 --- a/server/server.js +++ b/server/server.js @@ -321,7 +321,7 @@ exports.entryPage = "dashboard"; ]); if (user.twofa_status == 0) { - let newSecret = await genSecret(); + let newSecret = genSecret(); let encodedSecret = base32.encode(newSecret); // Google authenticator doesn't like equal signs From 300a95d779538af37850d4dc5923b6113246d763 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Mon, 18 Oct 2021 17:51:40 +0800 Subject: [PATCH 18/41] recompile util.js with tsconfig.json --- src/util.js | 41 ++++++++++++++++++++--------------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/src/util.js b/src/util.js index 28239efa..b2df7ac7 100644 --- a/src/util.js +++ b/src/util.js @@ -6,10 +6,10 @@ // // Backend uses the compiled file util.js // Frontend uses util.ts -exports.__esModule = true; +Object.defineProperty(exports, "__esModule", { value: true }); exports.getMonitorRelativeURL = exports.genSecret = exports.getCryptoRandomInt = exports.getRandomInt = exports.getRandomArbitrary = exports.TimeLogger = exports.polyfill = exports.debug = exports.ucfirst = exports.sleep = exports.flipStatus = exports.STATUS_PAGE_PARTIAL_DOWN = exports.STATUS_PAGE_ALL_UP = exports.STATUS_PAGE_ALL_DOWN = exports.PENDING = exports.UP = exports.DOWN = exports.appName = exports.isDev = void 0; -var _dayjs = require("dayjs"); -var dayjs = _dayjs; +const _dayjs = require("dayjs"); +const dayjs = _dayjs; exports.isDev = process.env.NODE_ENV === "development"; exports.appName = "Uptime Kuma"; exports.DOWN = 0; @@ -106,12 +106,12 @@ exports.getRandomInt = getRandomInt; * Returns either the NodeJS crypto.randomBytes() function or its * browser equivalent implemented via window.crypto.getRandomValues() */ -var getRandomBytes = ((typeof window !== 'undefined' && window.crypto) +let getRandomBytes = ((typeof window !== 'undefined' && window.crypto) // Browsers ? function () { - return function (numBytes) { - var randomBytes = new Uint8Array(numBytes); - for (var i = 0; i < numBytes; i += 65536) { + return (numBytes) => { + let randomBytes = new Uint8Array(numBytes); + for (let i = 0; i < numBytes; i += 65536) { window.crypto.getRandomValues(randomBytes.subarray(i, i + Math.min(numBytes - i, 65536))); } return randomBytes; @@ -123,13 +123,13 @@ var getRandomBytes = ((typeof window !== 'undefined' && window.crypto) })(); function getCryptoRandomInt(min, max) { // synchronous version of: https://github.com/joepie91/node-random-number-csprng - var range = max - min; + const range = max - min; if (range >= Math.pow(2, 32)) console.log("Warning! Range is too large."); - var tmpRange = range; - var bitsNeeded = 0; - var bytesNeeded = 0; - var mask = 1; + let tmpRange = range; + let bitsNeeded = 0; + let bytesNeeded = 0; + let mask = 1; while (tmpRange > 0) { if (bitsNeeded % 8 === 0) bytesNeeded += 1; @@ -137,9 +137,9 @@ function getCryptoRandomInt(min, max) { mask = mask << 1 | 1; tmpRange = tmpRange >>> 1; } - var randomBytes = getRandomBytes(bytesNeeded); - var randomValue = 0; - for (var i = 0; i < bytesNeeded; i++) { + const randomBytes = getRandomBytes(bytesNeeded); + let randomValue = 0; + for (let i = 0; i < bytesNeeded; i++) { randomValue |= randomBytes[i] << 8 * i; } randomValue = randomValue & mask; @@ -151,12 +151,11 @@ function getCryptoRandomInt(min, max) { } } exports.getCryptoRandomInt = getCryptoRandomInt; -function genSecret(length) { - if (length === void 0) { length = 64; } - var secret = ""; - var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - var charsLength = chars.length; - for (var i = 0; i < length; i++) { +function genSecret(length = 64) { + let secret = ""; + const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + const charsLength = chars.length; + for (let i = 0; i < length; i++) { secret += chars.charAt(getCryptoRandomInt(0, charsLength - 1)); } return secret; From 6277babf25ed6be0a9183b991482758fc663a38a Mon Sep 17 00:00:00 2001 From: Juan Calderon-Perez <835733+gaby@users.noreply.github.com> Date: Mon, 18 Oct 2021 08:22:08 -0400 Subject: [PATCH 19/41] Migrate to actions/stale v4.0 --- .github/workflows/stale-bot | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stale-bot b/.github/workflows/stale-bot index 5992b93e..46a97f52 100644 --- a/.github/workflows/stale-bot +++ b/.github/workflows/stale-bot @@ -8,7 +8,7 @@ jobs: stale: runs-on: ubuntu-latest steps: - - uses: actions/stale@v3 + - uses: actions/stale@v4 with: stale-issue-message: 'We are clearing up our old issues and your ticket has been open for 6 months with no activity. Remove stale label or comment or this will be closed in 7 days.' stale-pr-message: ''We are clearing up our old Pull Requests and yours has been open for 6 months with no activity. Remove stale label or comment or this will be closed in 7 days.' From b5857f7c0c2bb83e20e2ed7cea775d079e5212d6 Mon Sep 17 00:00:00 2001 From: Juan Calderon-Perez <835733+gaby@users.noreply.github.com> Date: Mon, 18 Oct 2021 08:23:18 -0400 Subject: [PATCH 20/41] Fix syntax issue. --- .github/workflows/stale-bot | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stale-bot b/.github/workflows/stale-bot index 46a97f52..5dc50136 100644 --- a/.github/workflows/stale-bot +++ b/.github/workflows/stale-bot @@ -11,7 +11,7 @@ jobs: - uses: actions/stale@v4 with: stale-issue-message: 'We are clearing up our old issues and your ticket has been open for 6 months with no activity. Remove stale label or comment or this will be closed in 7 days.' - stale-pr-message: ''We are clearing up our old Pull Requests and yours has been open for 6 months with no activity. Remove stale label or comment or this will be closed in 7 days.' + stale-pr-message: 'We are clearing up our old Pull Requests and yours has been open for 6 months with no activity. Remove stale label or comment or this will be closed in 7 days.' close-issue-message: 'This issue was closed because it has been stalled for 7 days with no activity.' close-pr-message: 'This PR was closed because it has been stalled for 7 days with no activity.' days-before-stale: 180 From 572a5300aa9648b55627106cd9501fd5125b75ca Mon Sep 17 00:00:00 2001 From: NeuralMiner Date: Mon, 18 Oct 2021 13:25:53 -0600 Subject: [PATCH 21/41] Recommended updates. --- server/database.js | 2 +- server/server.js | 2 +- src/languages/en.js | 1 + src/pages/Setup.vue | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/server/database.js b/server/database.js index 7a0f8379..aa363846 100644 --- a/server/database.js +++ b/server/database.js @@ -312,7 +312,7 @@ class Database { */ static backup(version) { if (! this.backupPath) { - console.info("Backup the database"); + console.info("Backing up the database"); this.backupPath = this.dataDir + "kuma.db.bak" + version; fs.copyFileSync(Database.path, this.backupPath); diff --git a/server/server.js b/server/server.js index 59d72033..4f1943cc 100644 --- a/server/server.js +++ b/server/server.js @@ -452,7 +452,7 @@ exports.entryPage = "dashboard"; socket.on("setup", async (username, password, callback) => { try { if ((await R.count("user")) !== 0) { - throw new Error("Uptime Kuma has been set up. If you want to setup again, please delete the database."); + throw new Error("Uptime Kuma has been initialized. If you want to run setup again, please delete the database."); } let user = R.dispense("user"); diff --git a/src/languages/en.js b/src/languages/en.js index 4542d72b..0c39a32b 100644 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -301,6 +301,7 @@ export default { BodyInvalidFormat: "The request body is not valid JSON: ", "Monitor History": "Monitor History:", clearDataOlderThan: "Keep monitor history data for {0} days.", + PasswordsDoNotMatch: "Passwords do not match.", records: "records", "One record": "One record", "Showing {from} to {to} of {count} records": "Showing {from} to {to} of {count} records", diff --git a/src/pages/Setup.vue b/src/pages/Setup.vue index ed8ea3ef..ab595216 100644 --- a/src/pages/Setup.vue +++ b/src/pages/Setup.vue @@ -75,7 +75,7 @@ export default { this.processing = true; if (this.password !== this.repeatPassword) { - toast.error("Passwords do not match."); + toast.error(this.$t("PasswordsDoNotMatch")); this.processing = false; return; } From 76e7c8b276a02bf2eedebf61cf319e00591a54ed Mon Sep 17 00:00:00 2001 From: NeuralMiner Date: Mon, 18 Oct 2021 14:35:36 -0600 Subject: [PATCH 22/41] Rebase --- server/server.js | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/server/server.js b/server/server.js index 4f1943cc..c4d18869 100644 --- a/server/server.js +++ b/server/server.js @@ -1,6 +1,7 @@ console.log("Welcome to Uptime Kuma"); const args = require("args-parser")(process.argv); const { sleep, debug, getRandomInt, genSecret } = require("../src/util"); +const config = require("./config"); debug(args); @@ -8,10 +9,6 @@ if (! process.env.NODE_ENV) { process.env.NODE_ENV = "production"; } -// Demo Mode? -const demoMode = args["demo"] || false; -exports.demoMode = demoMode; - console.log("Node Env: " + process.env.NODE_ENV); console.log("Importing Node libraries"); @@ -93,7 +90,7 @@ const twofa_verification_opts = { */ const testMode = !!args["test"] || false; -if (demoMode) { +if (config.demoMode) { console.log("==== Demo Mode ===="); } @@ -452,7 +449,7 @@ exports.entryPage = "dashboard"; socket.on("setup", async (username, password, callback) => { try { if ((await R.count("user")) !== 0) { - throw new Error("Uptime Kuma has been initialized. If you want to run setup again, please delete the database."); + throw new Error("Uptime Kuma has been setup. If you want to setup again, please delete the database."); } let user = R.dispense("user"); @@ -1342,7 +1339,7 @@ async function initDatabase() { fs.copyFileSync(Database.templatePath, Database.path); } - console.log("Connecting to the Database"); + console.log("Connecting to Database"); await Database.connect(); console.log("Connected"); @@ -1442,7 +1439,7 @@ async function shutdownFunction(signal) { } function finalFunction() { - console.log("Graceful shutdown successful!"); + console.log("Graceful shutdown successfully!"); } gracefulShutdown(server, { From 9ddffc0f7f36f93fe7a4256edfe8a250c2c03af2 Mon Sep 17 00:00:00 2001 From: NeuralMiner Date: Mon, 18 Oct 2021 14:35:47 -0600 Subject: [PATCH 23/41] Updates --- server/server.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/server.js b/server/server.js index c4d18869..3c3c2e37 100644 --- a/server/server.js +++ b/server/server.js @@ -449,7 +449,7 @@ exports.entryPage = "dashboard"; socket.on("setup", async (username, password, callback) => { try { if ((await R.count("user")) !== 0) { - throw new Error("Uptime Kuma has been setup. If you want to setup again, please delete the database."); + throw new Error("Uptime Kuma has been initialized. If you want to run setup again, please delete the database."); } let user = R.dispense("user"); @@ -1339,7 +1339,7 @@ async function initDatabase() { fs.copyFileSync(Database.templatePath, Database.path); } - console.log("Connecting to Database"); + console.log("Connecting to the Database"); await Database.connect(); console.log("Connected"); @@ -1439,7 +1439,7 @@ async function shutdownFunction(signal) { } function finalFunction() { - console.log("Graceful shutdown successfully!"); + console.log("Graceful shutdown successful!"); } gracefulShutdown(server, { From a35569481d587688a8dc814532b876e0a3485c0f Mon Sep 17 00:00:00 2001 From: NeuralMiner Date: Mon, 18 Oct 2021 15:26:38 -0600 Subject: [PATCH 24/41] Updates --- src/languages/en.js | 64 ++++++++++++++++++++++----------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/src/languages/en.js b/src/languages/en.js index 0c39a32b..d3a69c19 100644 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -1,28 +1,28 @@ export default { languageName: "English", - checkEverySecond: "Check every {0} seconds.", - retryCheckEverySecond: "Retry every {0} seconds.", + checkEverySecond: "Check every {0} seconds", + retryCheckEverySecond: "Retry every {0} seconds", retriesDescription: "Maximum retries before the service is marked as down and a notification is sent", ignoreTLSError: "Ignore TLS/SSL error for HTTPS websites", upsideDownModeDescription: "Flip the status upside down. If the service is reachable, it is DOWN.", maxRedirectDescription: "Maximum number of redirects to follow. Set to 0 to disable redirects.", acceptedStatusCodesDescription: "Select status codes which are considered as a successful response.", passwordNotMatchMsg: "The repeat password does not match.", - notificationDescription: "Please assign a notification to monitor(s) to get it to work.", - keywordDescription: "Search keyword in plain html or JSON response and it is case-sensitive", + notificationDescription: "Notifications must be assigned to a monitor to function.", + keywordDescription: "Search keyword in plain HTML or JSON response. The search is case-sensitive.", pauseDashboardHome: "Pause", deleteMonitorMsg: "Are you sure want to delete this monitor?", deleteNotificationMsg: "Are you sure want to delete this notification for all monitors?", - resoverserverDescription: "Cloudflare is the default server, you can change the resolver server anytime.", - rrtypeDescription: "Select the RR-Type you want to monitor", + resoverserverDescription: "Cloudflare is the default server. You can change the resolver server anytime.", + 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.", + enableDefaultNotificationDescription: "This notification will be enabled by default for new monitors. 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 you want to delete ALL statistics?", importHandleDescription: "Choose 'Skip existing' if you want to skip every monitor or notification with the same name. 'Overwrite' will delete every existing monitor and notification.", - confirmImportMsg: "Are you sure to import the backup? Please make sure you've selected the right import option.", - twoFAVerifyLabel: "Please type in your token to verify that 2FA is working", + confirmImportMsg: "Are you sure you want to import the backup? Please verify you've selected the correct import option.", + twoFAVerifyLabel: "Please enter your token to verify 2FA:", tokenValidSettingsMsg: "Token is valid! You can now save the 2FA settings.", confirmEnableTwoFAMsg: "Are you sure you want to enable 2FA?", confirmDisableTwoFAMsg: "Are you sure you want to disable 2FA?", @@ -77,7 +77,7 @@ export default { "Max. Redirects": "Max. Redirects", "Accepted Status Codes": "Accepted Status Codes", "Push URL": "Push URL", - needPushEvery: "You should call this url every {0} seconds.", + needPushEvery: "You should call this URL every {0} seconds.", pushOptionalParams: "Optional parameters: {0}", Save: "Save", Notifications: "Notifications", @@ -135,9 +135,9 @@ export default { Events: "Events", Heartbeats: "Heartbeats", "Auto Get": "Auto Get", - backupDescription: "You can backup all monitors and all notifications into a JSON file.", - backupDescription2: "PS: History and event data is not included.", - backupDescription3: "Sensitive data such as notification tokens is included in the export file, please keep it carefully.", + backupDescription: "You can backup all monitors and notifications into a JSON file.", + backupDescription2: "Note: history and event data is not included.", + backupDescription3: "Sensitive data such as notification tokens are included in the export file; please store export securely.", alertNoFile: "Please select a file to import.", alertWrongFileType: "Please select a JSON file.", "Clear all statistics": "Clear all Statistics", @@ -157,8 +157,8 @@ export default { "Show URI": "Show URI", Tags: "Tags", "Add New below or Select...": "Add New below or Select...", - "Tag with this name already exist.": "Tag with this name already exist.", - "Tag with this value already exist.": "Tag with this value already exist.", + "Tag with this name already exist.": "Tag with this name already exists.", + "Tag with this value already exist.": "Tag with this value already exists.", color: "color", "value (optional)": "value (optional)", Gray: "Gray", @@ -192,14 +192,14 @@ export default { wayToGetTelegramToken: "You can get a token from {0}.", "Chat ID": "Chat ID", supportTelegramChatID: "Support Direct Chat / Group / Channel's Chat ID", - wayToGetTelegramChatID: "You can get your chat id by sending message to the bot and go to this url to view the chat_id:", + wayToGetTelegramChatID: "You can get your chat ID by sending a message to the bot and going to this URL to view the chat_id:", "YOUR BOT TOKEN HERE": "YOUR BOT TOKEN HERE", - chatIDNotFound: "Chat ID is not found, please send a message to this bot first", + chatIDNotFound: "Chat ID is not found; please send a message to this bot first", "webhook": "Webhook", "Post URL": "Post URL", "Content Type": "Content Type", - webhookJsonDesc: "{0} is good for any modern http servers such as express.js", - webhookFormDataDesc: "{multipart} is good for PHP, you just need to parse the json by {decodeFunction}", + webhookJsonDesc: "{0} is good for any modern HTTP servers such as Express.js", + webhookFormDataDesc: "{multipart} is good for PHP. The JSON will need to be parsed with {decodeFunction}", "smtp": "Email (SMTP)", secureOptionNone: "None / STARTTLS (25, 587)", secureOptionTLS: "TLS (465)", @@ -217,12 +217,12 @@ export default { "Hello @everyone is...": "Hello {'@'}everyone is...", "teams": "Microsoft Teams", "Webhook URL": "Webhook URL", - wayToGetTeamsURL: "You can learn how to create a webhook url {0}.", + wayToGetTeamsURL: "You can learn how to create a webhook URL {0}.", "signal": "Signal", "Number": "Number", "Recipients": "Recipients", needSignalAPI: "You need to have a signal client with REST API.", - wayToCheckSignalURL: "You can check this url to view how to setup one:", + wayToCheckSignalURL: "You can check this URL to view how to set one up:", signalImportant: "IMPORTANT: You cannot mix groups and numbers in recipients!", "gotify": "Gotify", "Application Token": "Application Token", @@ -232,11 +232,11 @@ export default { "Icon Emoji": "Icon Emoji", "Channel Name": "Channel Name", "Uptime Kuma URL": "Uptime Kuma URL", - aboutWebhooks: "More info about webhooks on: {0}", - aboutChannelName: "Enter the channel name on {0} Channel Name field if you want to bypass the webhook channel. Ex: #other-channel", - aboutKumaURL: "If you leave the Uptime Kuma URL field blank, it will default to the Project Github page.", + aboutWebhooks: "More info about Webhooks on: {0}", + aboutChannelName: "Enter the channel name on {0} Channel Name field if you want to bypass the Webhook channel. Ex: #other-channel", + aboutKumaURL: "If you leave the Uptime Kuma URL field blank, it will default to the Project GitHub page.", emojiCheatSheet: "Emoji cheat sheet: {0}", - "rocket.chat": "Rocket.chat", + "rocket.chat": "Rocket.Chat", pushover: "Pushover", pushy: "Pushy", octopush: "Octopush", @@ -255,7 +255,7 @@ export default { pushoverDesc2: "If you want to send notifications to different devices, fill out Device field.", "SMS Type": "SMS Type", octopushTypePremium: "Premium (Fast - recommended for alerting)", - octopushTypeLowCost: "Low Cost (Slow, sometimes blocked by operator)", + octopushTypeLowCost: "Low Cost (Slow - sometimes blocked by operator)", checkPrice: "Check {0} prices:", octopushLegacyHint: "Do you use the legacy version of Octopush (2011-2020) or the new version?", "Check octopush prices": "Check octopush prices {0}.", @@ -276,20 +276,20 @@ export default { "Basic Settings": "Basic Settings", "User ID": "User ID", "Messaging API": "Messaging API", - wayToGetLineChannelToken: "First access the {0}, create a provider and channel (Messaging API), then you can get the channel access token and user id from the above mentioned menu items.", + wayToGetLineChannelToken: "First access the {0}, create a provider and channel (Messaging API), then you can get the channel access token and user ID from the above mentioned menu items.", "Icon URL": "Icon URL", aboutIconURL: "You can provide a link to a picture in \"Icon URL\" to override the default profile picture. Will not be used if Icon Emoji is set.", - aboutMattermostChannelName: "You can override the default channel that webhook posts to by entering the channel name into \"Channel Name\" field. This needs to be enabled in Mattermost webhook settings. Ex: #other-channel", + aboutMattermostChannelName: "You can override the default channel that the Webhook posts to by entering the channel name into \"Channel Name\" field. This needs to be enabled in the Mattermost Webhook settings. Ex: #other-channel", "matrix": "Matrix", promosmsTypeEco: "SMS ECO - cheap but slow and often overloaded. Limited only to Polish recipients.", promosmsTypeFlash: "SMS FLASH - Message will automatically show on recipient device. Limited only to Polish recipients.", - promosmsTypeFull: "SMS FULL - Premium tier of SMS, You can use Your Sender Name (You need to register name first). Reliable for alerts.", + promosmsTypeFull: "SMS FULL - Premium tier of SMS, You can use your Sender Name (You need to register name first). Reliable for alerts.", promosmsTypeSpeed: "SMS SPEED - Highest priority in system. Very quick and reliable but costly (about twice of SMS FULL price).", promosmsPhoneNumber: "Phone number (for Polish recipient You can skip area codes)", promosmsSMSSender: "SMS Sender Name : Pre-registred name or one of defaults: InfoSMS, SMS Info, MaxSMS, INFO, SMS", - "Feishu WebHookUrl": "Feishu WebHookUrl", + "Feishu WebHookUrl": "Feishu WebHookURL", matrixHomeserverURL: "Homeserver URL (with http(s):// and optionally port)", - "Internal Room Id": "Internal Room Id", + "Internal Room Id": "Internal Room ID", matrixDesc1: "You can find the internal room ID by looking in the advanced section of the room settings in your Matrix client. It should look like !QMdRCpUIfLwsfjxye6:home.server.", matrixDesc2: "It is highly recommended you create a new user and do not use your own Matrix user's access token as it will allow full access to your account and all the rooms you joined. Instead, create a new user and only invite it to the room that you want to receive the notification in. You can get the access token by running {0}", // End notification form @@ -305,5 +305,5 @@ export default { records: "records", "One record": "One record", "Showing {from} to {to} of {count} records": "Showing {from} to {to} of {count} records", - steamApiKeyDescription: "For monitoring a Steam Gameserver you need a steam Web-API key. You can register your api key here: ", + steamApiKeyDescription: "For monitoring a Steam Game Server you need a Steam Web-API key. You can register your API key here: ", }; From 62f168a2a5103b3e2b2241cd893104655dc534c5 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Tue, 19 Oct 2021 14:26:10 +0800 Subject: [PATCH 25/41] config response header --- server/server.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/server/server.js b/server/server.js index c4d18869..e853378e 100644 --- a/server/server.js +++ b/server/server.js @@ -119,6 +119,13 @@ const { statusPageSocketHandler } = require("./socket-handlers/status-page-socke app.use(express.json()); +// Global Middleware +app.use(function (req, res, next) { + res.setHeader("X-Frame-Options", "SAMEORIGIN"); + res.removeHeader("X-Powered-By"); + next(); +}); + /** * Total WebSocket client connected to server currently, no actual use * @type {number} @@ -192,7 +199,7 @@ exports.entryPage = "dashboard"; const apiRouter = require("./routers/api-router"); app.use(apiRouter); - // Universal Route Handler, must be at the end of all express route. + // Universal Route Handler, must be at the end of all express routes. app.get("*", async (_request, response) => { if (_request.originalUrl.startsWith("/upload/")) { response.status(404).send("File not found."); From 5c3892313eb523a1b20ab675de90e4b070dd2ad9 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Tue, 19 Oct 2021 14:41:05 +0800 Subject: [PATCH 26/41] add env var: UPTIME_KUMA_DISABLE_FRAME_SAMEORIGIN --- docker/dockerfile | 5 +++-- server/server.js | 5 ++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/docker/dockerfile b/docker/dockerfile index 27ee9736..e2a3725f 100644 --- a/docker/dockerfile +++ b/docker/dockerfile @@ -31,14 +31,15 @@ WORKDIR / RUN apt update && \ apt --yes install curl file +COPY --from=build /app /app + +ARG VERSION=1.9.1 ARG GITHUB_TOKEN ARG TARGETARCH ARG PLATFORM=debian -ARG VERSION=1.9.0 ARG FILE=$PLATFORM-$TARGETARCH-$VERSION.tar.gz ARG DIST=dist.tar.gz -COPY --from=build /app /app RUN chmod +x /app/extra/upload-github-release-asset.sh # Full Build diff --git a/server/server.js b/server/server.js index 8156e4e3..b7f53199 100644 --- a/server/server.js +++ b/server/server.js @@ -77,6 +77,7 @@ const port = parseInt(process.env.UPTIME_KUMA_PORT || process.env.PORT || args.p // SSL const sslKey = process.env.UPTIME_KUMA_SSL_KEY || process.env.SSL_KEY || args["ssl-key"] || undefined; const sslCert = process.env.UPTIME_KUMA_SSL_CERT || process.env.SSL_CERT || args["ssl-cert"] || undefined; +const disableFrameSameOrigin = !!process.env.UPTIME_KUMA_DISABLE_FRAME_SAMEORIGIN || false; // 2FA / notp verification defaults const twofa_verification_opts = { @@ -121,7 +122,9 @@ app.use(express.json()); // Global Middleware app.use(function (req, res, next) { - res.setHeader("X-Frame-Options", "SAMEORIGIN"); + if (disableFrameSameOrigin) { + res.setHeader("X-Frame-Options", "SAMEORIGIN"); + } res.removeHeader("X-Powered-By"); next(); }); From 6c1ee70e15eb44e9b231ffaa10b3ea142ec341bb Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Tue, 19 Oct 2021 16:29:09 +0800 Subject: [PATCH 27/41] fix --- server/server.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/server.js b/server/server.js index b7f53199..1a6d73db 100644 --- a/server/server.js +++ b/server/server.js @@ -77,7 +77,7 @@ const port = parseInt(process.env.UPTIME_KUMA_PORT || process.env.PORT || args.p // SSL const sslKey = process.env.UPTIME_KUMA_SSL_KEY || process.env.SSL_KEY || args["ssl-key"] || undefined; const sslCert = process.env.UPTIME_KUMA_SSL_CERT || process.env.SSL_CERT || args["ssl-cert"] || undefined; -const disableFrameSameOrigin = !!process.env.UPTIME_KUMA_DISABLE_FRAME_SAMEORIGIN || false; +const disableFrameSameOrigin = !!process.env.UPTIME_KUMA_DISABLE_FRAME_SAMEORIGIN || args["disable-frame-sameorigin"] || false; // 2FA / notp verification defaults const twofa_verification_opts = { @@ -122,7 +122,7 @@ app.use(express.json()); // Global Middleware app.use(function (req, res, next) { - if (disableFrameSameOrigin) { + if (!disableFrameSameOrigin) { res.setHeader("X-Frame-Options", "SAMEORIGIN"); } res.removeHeader("X-Powered-By"); From 5a05d135b8cf6801a0d6aeec37b1aaa16789f970 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Patr=C3=B3n=20G=C3=B3mez?= Date: Tue, 19 Oct 2021 17:11:32 +0200 Subject: [PATCH 28/41] Add steam translation in spanish --- src/languages/es-ES.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/languages/es-ES.js b/src/languages/es-ES.js index b438dbae..ccd42c8f 100644 --- a/src/languages/es-ES.js +++ b/src/languages/es-ES.js @@ -203,4 +203,5 @@ export default { records: "registros", "One record": "Un registro", "Showing {from} to {to} of {count} records": "Mostrando desde {from} a {to} de {count} registros", + steamApiKeyDescription: "Para monitorear un servidor de juegos de Steam, necesita una clave Steam Web-API. Puede registrar su clave API aquí: ", }; From cd04ac455745f7bdbb6fd29e192a49f067235d7f Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Wed, 20 Oct 2021 01:32:19 +0800 Subject: [PATCH 29/41] "dist/index.html" is no longer needed for development environment --- server/server.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/server/server.js b/server/server.js index d5f2d373..1846fe6e 100644 --- a/server/server.js +++ b/server/server.js @@ -157,7 +157,17 @@ let needSetup = false; * Cache Index HTML * @type {string} */ -let indexHTML = fs.readFileSync("./dist/index.html").toString(); +let indexHTML = ""; + +try { + indexHTML = fs.readFileSync("./dist/index.html").toString(); +} catch (e) { + // "dist/index.html" is not necessary for development + if (process.env.NODE_ENV !== "development") { + console.error("Error: Cannot find 'dist/index.html', did you install correctly?"); + process.exit(1); + } +} exports.entryPage = "dashboard"; From d0b0c64b81043ce192f47d7c26964581488abe9a Mon Sep 17 00:00:00 2001 From: MrEddX <66828538+MrEddX@users.noreply.github.com> Date: Tue, 19 Oct 2021 23:03:46 +0300 Subject: [PATCH 30/41] Update bg-BG.js - Fixed existing fields - Added new fields - Translated new fields --- src/languages/bg-BG.js | 120 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 113 insertions(+), 7 deletions(-) diff --git a/src/languages/bg-BG.js b/src/languages/bg-BG.js index 48d3f238..a7be06f1 100644 --- a/src/languages/bg-BG.js +++ b/src/languages/bg-BG.js @@ -2,9 +2,9 @@ export default { languageName: "Български", checkEverySecond: "Ще се извършва на всеки {0} секунди", retryCheckEverySecond: "Ще се извършва на всеки {0} секунди", - retriesDescription: "Максимакен брой опити преди услугата да бъде маркирана като недостъпна и да бъде изпратено известие", + retriesDescription: "Максимакен брой опити преди маркиране на услугата като недостъпна и изпращане на известие", ignoreTLSError: "Игнорирай TLS/SSL грешки за HTTPS уебсайтове", - upsideDownModeDescription: "Обърни статуса от достъпен на недостъпен. Ако услугата е достъпна се вижда НЕДОСТЪПНА.", + upsideDownModeDescription: "Обръща статуса от достъпен на недостъпен. Ако услугата е достъпна, ще се вижда като НЕДОСТЪПНА.", maxRedirectDescription: "Максимален брой пренасочвания, които да бъдат следвани. Въведете 0 за да изключите пренасочване.", acceptedStatusCodesDescription: "Изберете статус кодове, които се считат за успешен отговор.", passwordNotMatchMsg: "Повторената парола не съвпада.", @@ -48,7 +48,7 @@ export default { Status: "Статус", DateTime: "Дата и час", Message: "Отговор", - "No important events": "Няма важни събития", + "No important events": "Все още няма събития", Resume: "Възобнови", Edit: "Редактирай", Delete: "Изтрий", @@ -107,8 +107,8 @@ export default { Password: "Парола", "Remember me": "Запомни ме", Login: "Вход", - "No Monitors, please": "Моля, без монитори", - "add one": "добави един", + "No Monitors, please": "Все още няма монитори. Моля, добавете поне ", + "add one": "един.", "Notification Type": "Тип известяване", Email: "Имейл", Test: "Тест", @@ -179,8 +179,8 @@ export default { "Edit Status Page": "Редактиране Статус страница", "Go to Dashboard": "Към Таблото", telegram: "Telegram", - webhook: "Webhook", - smtp: "Email (SMTP)", + webhook: "Уеб кука", + smtp: "Имейл (SMTP)", discord: "Discord", teams: "Microsoft Teams", signal: "Signal", @@ -197,4 +197,110 @@ export default { line: "Line Messenger", mattermost: "Mattermost", "Status Page": "Статус страница", + "Primary Base URL": "Основен базов URL адрес", + "Push URL": "Генериран Push URL адрес", + needPushEvery: "Необходимо е да извършвате заявка към този URL адрес на всеки {0} секунди.", + pushOptionalParams: "Допълнителни, но незадължителни параметри: {0}", + defaultNotificationName: "Моето {notification} известяване ({number})", + here: "тук", + Required: "Задължително поле", + "Bot Token": "Бот токен", + wayToGetTelegramToken: "Можете да получите токен от {0}.", + "Chat ID": "Чат ID", + supportTelegramChatID: "Поддържа Direct Chat / Group / Channel's Chat ID", + wayToGetTelegramChatID: "Можете да получите вашето чат ID, като изпратите съобщение на бота, след което е нужно да посетите този URL адрес за да го видите:", + "YOUR BOT TOKEN HERE": "ВАШИЯТ БОТ ТОКЕН ТУК", + chatIDNotFound: "Чат ID не е намерено. Моля, първо изпратете съобщение до този бот", + "Post URL": "Post URL адрес", + "Content Type": "Тип съдържание", + webhookJsonDesc: "{0} е подходящ за всички съвременни http сървъри, като например express.js", + webhookFormDataDesc: "{multipart} е подходящ за PHP, нужно е да анализирате json чрез {decodeFunction}", + secureOptionNone: "Няма (25) / STARTTLS (587)", + secureOptionTLS: "TLS (465)", + "Ignore TLS Error": "Игнорирай TLS грешките", + "From Email": "От имейл адрес", + emailCustomSubject: "Модифициране на тема", + "To Email": "Получател имейл адрес", + smtpCC: "Явно копие до имейл адрес:", + smtpBCC: "Скрито копие до имейл адрес:", + "Discord Webhook URL": "Discord URL адрес на уеб кука", + wayToGetDiscordURL: "Може да създадете, от меню \"Настройки на сървъра\" -> \"Интеграции\" -> \"Уеб куки\" -> \"Нова уеб кука\"", + "Bot Display Name": "Име на бота, което да се показва", + "Prefix Custom Message": "Модифицирано обръщение", + "Hello @everyone is...": "Здравейте, {'@'}everyone е...", + "Webhook URL": "Уеб кука URL адрес", + wayToGetTeamsURL: "Можете да научите как се създава URL адрес за уеб кука {0}.", + Number: "Номер", + Recipients: "Получатели", + needSignalAPI: "Необходимо е да разполагате със Signal клиент с REST API.", + wayToCheckSignalURL: "Може да посетите този URL адрес, ако се нуждаете от помощ при настройването:", + signalImportant: "ВАЖНО: Не може да смесвате \"Групи\" и \"Номера\" в поле \"Получатели\"!", + "Application Token": "Токен код за приложението", + "Server URL": "URL адрес на сървъра", + Priority: "Приоритет", + "Icon Emoji": "Иконка Емотикон", + "Channel Name": "Канал име", + "Uptime Kuma URL": "Uptime Kuma URL адрес", + aboutWebhooks: "Повече информация относно уеб куки на: {0}", + aboutChannelName: "Въведете името на канала в поле {0} \"Канал име\", ако желаете да заобиколите канала от уеб куката. Например: #other-channel", + aboutKumaURL: "Ако оставите празно полето \"Uptime Kuma URL адрес\", по подразбиране ще се използва GitHub страницата на проекта.", + emojiCheatSheet: "Подсказки за емотикони: {0}", + "User Key": "Потребителски ключ", + Device: "Устройство", + "Message Title": "Заглавие на съобщението", + "Notification Sound": "Звуков сигнал", + "More info on:": "Повече информация на: {0}", + pushoverDesc1: "Приоритет Спешно (2) по подразбиране изчаква 30 секунди между повторните опити и изтича след 1 час.", + pushoverDesc2: "Ако желаете да изпратите известявания до различни устройства, попълнете полето Устройство.", + "SMS Type": "СМС тип", + octopushTypePremium: "Премиум (Бърз - препоръчителен в случай на тревога)", + octopushTypeLowCost: "Евтин (Бавен - понякога бива блокиран от оператора)", + checkPrice: "Тарифни планове на {0}:", + octopushLegacyHint: "Дали използвате съвместима версия на Octopush (2011-2020) или нова версия?", + "Check octopush prices": "Тарифни планове на octopush {0}.", + octopushPhoneNumber: "Телефонен номер (в международен формат, например: +33612345678) ", + octopushSMSSender: "СМС подател Име: 3-11 знака - букви, цифри и интервал (a-zA-Z0-9)", + "LunaSea Device ID": "LunaSea ID на устройство", + "Apprise URL": "Apprise URL адрес", + "Example:": "Пример: {0}", + "Read more:": "Научете повече: {0}", + "Status:": "Статус: {0}", + "Read more": "Научете повече", + appriseInstalled: "Apprise е инсталиран.", + appriseNotInstalled: "Apprise не е инсталиран. {0}", + "Access Token": "Токен код за достъп", + "Channel access token": "Канал токен код", + "Line Developers Console": "Line - Конзола за разработчици", + lineDevConsoleTo: "Line - Конзола за разработчици - {0}", + "Basic Settings": "Основни настройки", + "User ID": "Потребител ID", + "Messaging API": "API за известяване", + wayToGetLineChannelToken: "Необходимо е първо да посетите {0}, за да създадете (Messaging API) за доставчик и канал, след което може да вземете токен кода за канал и потребителско ID от споменатите по-горе елементи на менюто.", + "Icon URL": "URL адрес за иконка", + aboutIconURL: "Може да предоставите линк към картинка в поле \"URL Адрес за иконка\" за да отмените картинката на профила по подразбиране. Няма да се използва, ако вече сте настроили емотикон.", + aboutMattermostChannelName: "Може да замените канала по подразбиране, към който публикува уеб куката, като въведете името на канала в полето \"Канал име\". Tрябва да бъде активирано в настройките за уеб кука на Mattermost. Например: #other-channel", + matrix: "Matrix", + promosmsTypeEco: "СМС ECO - евтин, но бавен. Често е претоварен. Само за получатели от Полша.", + promosmsTypeFlash: "СМС FLASH - Съобщението автоматично се показва на устройството на получателя. Само за получатели от Полша.", + promosmsTypeFull: "СМС FULL - Високо ниво на СМС услуга. Може да използвате Вашето име като подател (Необходимо е първо да регистрирате името). Надежден метод за съобщения тип тревога.", + promosmsTypeSpeed: "СМС SPEED - Най-висок приоритет в системата. Много бърза и надеждна, но същвременно скъпа услуга. (Около два пъти по-висока цена в сравнение с SMS FULL).", + promosmsPhoneNumber: "Телефонен номер (за получатели от Полша, може да пропуснете въвеждането на код за населено място)", + promosmsSMSSender: "СМС Подател име: Предварително регистрирано име или някое от имената по подразбиране: InfoSMS, SMS Info, MaxSMS, INFO, SMS", + "Feishu WebHookUrl": "Feishu URL адрес за уеб кука", + matrixHomeserverURL: "Сървър URL адрес (започва с http(s):// и порт по желание)", + "Internal Room Id": "ID на вътрешна стая", + matrixDesc1: "Може да намерите \"ID на вътрешна стая\" в разширените настройки на стаята във вашия Matrix клиент. Примерен изглед: !QMdRCpUIfLwsfjxye6:home.server.", + matrixDesc2: "Силно препоръчваме да създадете НОВ потребител и да НЕ използвате токен кодът на вашия личен Matrix потребирел, т.к. той позволява пълен достъп до вашия акаунт и всички стаи към които сте се присъединили. Вместо това създайте нов потребител и го поканете само в стаята, където желаете да получавате известяванията. Токен код за достъп ще получите изпълнявайки {0}", + Method: "Метод", + Body: "Съобщение", + Headers: "Хедъри", + PushUrl: "Push URL адрес", + HeadersInvalidFormat: "Заявените хедъри не са валидни JSON: ", + BodyInvalidFormat: "Заявеното съобщение не е валиден JSON: ", + "Monitor History": "История на мониторите:", + clearDataOlderThan: "Ще се съхранява за {0} дни.", + records: "записа", + "One record": "Един запис", + "Showing {from} to {to} of {count} records": "Показване на {from} до {to} от {count} записа", + steamApiKeyDescription: "За да мониторирате Steam Gameserver се нуждаете от Steam Web-API ключ. Може да регистрирате Вашия API ключ тук: ", }; From 6463d4b2093dad1905941dd12a909cec1e1869c4 Mon Sep 17 00:00:00 2001 From: MrEddX <66828538+MrEddX@users.noreply.github.com> Date: Wed, 20 Oct 2021 09:09:08 +0300 Subject: [PATCH 31/41] Fix: Integration Link Text Maybe left there from the Slack template. --- src/components/notifications/RocketChat.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/notifications/RocketChat.vue b/src/components/notifications/RocketChat.vue index ed90fb75..78466060 100644 --- a/src/components/notifications/RocketChat.vue +++ b/src/components/notifications/RocketChat.vue @@ -11,7 +11,7 @@
*{{ $t("Required") }} - https://api.slack.com/messaging/webhooks + https://docs.rocket.chat/guides/administration/administration/integrations

{{ $t("aboutChannelName", [$t("rocket.chat")]) }} From 9982887783ff82c7962f485ec080d98b5c932fcf Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Wed, 20 Oct 2021 18:08:52 +0800 Subject: [PATCH 32/41] Update feature_request.md --- .github/ISSUE_TEMPLATE/feature_request.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 9141130a..4794cc24 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -6,7 +6,7 @@ labels: enhancement assignees: '' --- -**Is it a duplicate question?** +**Is it a duplicated question?** Please search in Issues without filters: https://github.com/louislam/uptime-kuma/issues?q= **Is your feature request related to a problem? Please describe.** From 3fe0e9bf1ec82e782881178fde51e3b29b7680c5 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Wed, 20 Oct 2021 18:09:10 +0800 Subject: [PATCH 33/41] Update ask-for-help.md --- .github/ISSUE_TEMPLATE/ask-for-help.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/ask-for-help.md b/.github/ISSUE_TEMPLATE/ask-for-help.md index 79ec21c6..3031e077 100644 --- a/.github/ISSUE_TEMPLATE/ask-for-help.md +++ b/.github/ISSUE_TEMPLATE/ask-for-help.md @@ -6,7 +6,7 @@ labels: help assignees: '' --- -**Is it a duplicate question?** +**Is it a duplicated question?** Please search in Issues without filters: https://github.com/louislam/uptime-kuma/issues?q= **Describe your problem** From 7a1bb964e99de19849f9b19778c644eba5fa9bc2 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Wed, 20 Oct 2021 18:09:23 +0800 Subject: [PATCH 34/41] Update bug_report.md --- .github/ISSUE_TEMPLATE/bug_report.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 370b88b8..069ed6cc 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -7,7 +7,7 @@ assignees: '' --- -**Is it a duplicate question?** +**Is it a duplicated question?** Please search in Issues without filters: https://github.com/louislam/uptime-kuma/issues?q= **Describe the bug** From 29e4e412150e76f2e87f9f9aa82a158b55b67d77 Mon Sep 17 00:00:00 2001 From: DX37 Date: Wed, 20 Oct 2021 23:00:29 +0700 Subject: [PATCH 35/41] Update ru-RU.js --- src/languages/ru-RU.js | 117 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 112 insertions(+), 5 deletions(-) diff --git a/src/languages/ru-RU.js b/src/languages/ru-RU.js index 2dd3eafb..95146239 100644 --- a/src/languages/ru-RU.js +++ b/src/languages/ru-RU.js @@ -1,6 +1,6 @@ export default { languageName: "Русский", - checkEverySecond: "проверять каждые {0} секунд", + checkEverySecond: "Проверка каждые {0} секунд", retriesDescription: "Максимальное количество попыток перед пометкой сервиса как недоступного и отправкой уведомления", ignoreTLSError: "Игнорировать ошибку TLS/SSL для HTTPS сайтов", upsideDownModeDescription: "Реверс статуса сервиса. Если сервис доступен, то он помечается как НЕДОСТУПНЫЙ.", @@ -29,7 +29,7 @@ export default { "Add New Monitor": "Новый монитор", "Quick Stats": "Статистика", Up: "Доступен", - Down: "Н/Д", + Down: "Недоступен", Pending: "Ожидание", Unknown: "Неизвестно", Pause: "Пауза", @@ -65,8 +65,8 @@ export default { "Accepted Status Codes": "Допустимые коды статуса", Save: "Сохранить", Notifications: "Уведомления", - "Not available, please setup.": "Доступных уведомлений нет, необходима настройка.", - "Setup Notification": "Настроить уведомления", + "Not available, please setup.": "Доступных уведомлений нет, необходимо создать.", + "Setup Notification": "Создать уведомление", Light: "Светлая", Dark: "Тёмная", Auto: "Авто", @@ -142,7 +142,7 @@ export default { Token: "Токен", "Show URI": "Показать URI", "Clear all statistics": "Удалить всю статистику", - retryCheckEverySecond: "повторять каждые {0} секунд", + retryCheckEverySecond: "Повтор каждые {0} секунд", importHandleDescription: "Выберите \"Пропустить существующие\", если вы хотите пропустить каждый монитор или уведомление с таким же именем. \"Перезаписать\" удалит каждый существующий монитор или уведомление и добавит заново. Вариант \"Не проверять\" принудительно восстанавливает все мониторы и уведомления, даже если они уже существуют.", confirmImportMsg: "Вы действительно хотите восстановить резервную копию? Убедитесь, что вы выбрали подходящий вариант импорта.", "Heartbeat Retry Interval": "Интервал повтора опроса", @@ -202,4 +202,111 @@ export default { pushbullet: "Pushbullet", line: "Line Messenger", mattermost: "Mattermost", + "Primary Base URL": "Primary Base URL", + "Push URL": "Push URL", + needPushEvery: "You should call this URL every {0} seconds.", + pushOptionalParams: "Optional parameters: {0}", + defaultNotificationName: "My {notification} Alert ({number})", + here: "here", + Required: "Required", + "Bot Token": "Bot Token", + wayToGetTelegramToken: "You can get a token from {0}.", + "Chat ID": "Chat ID", + supportTelegramChatID: "Support Direct Chat / Group / Channel's Chat ID", + wayToGetTelegramChatID: "You can get your chat ID by sending a message to the bot and going to this URL to view the chat_id:", + "YOUR BOT TOKEN HERE": "YOUR BOT TOKEN HERE", + chatIDNotFound: "Chat ID is not found; please send a message to this bot first", + "Post URL": "Post URL", + "Content Type": "Content Type", + webhookJsonDesc: "{0} is good for any modern HTTP servers such as Express.js", + webhookFormDataDesc: "{multipart} is good for PHP. The JSON will need to be parsed with {decodeFunction}", + secureOptionNone: "None / STARTTLS (25, 587)", + secureOptionTLS: "TLS (465)", + "Ignore TLS Error": "Ignore TLS Error", + "From Email": "From Email", + emailCustomSubject: "Custom Subject", + "To Email": "To Email", + smtpCC: "CC", + smtpBCC: "BCC", + "Discord Webhook URL": "Discord Webhook URL", + wayToGetDiscordURL: "You can get this by going to Server Settings -> Integrations -> Create Webhook", + "Bot Display Name": "Bot Display Name", + "Prefix Custom Message": "Prefix Custom Message", + "Hello @everyone is...": "Hello {'@'}everyone is...", + "Webhook URL": "Webhook URL", + wayToGetTeamsURL: "You can learn how to create a webhook URL {0}.", + Number: "Number", + Recipients: "Recipients", + needSignalAPI: "You need to have a signal client with REST API.", + wayToCheckSignalURL: "You can check this URL to view how to set one up:", + signalImportant: "IMPORTANT: You cannot mix groups and numbers in recipients!", + "Application Token": "Application Token", + "Server URL": "Server URL", + Priority: "Priority", + "Icon Emoji": "Icon Emoji", + "Channel Name": "Channel Name", + "Uptime Kuma URL": "Uptime Kuma URL", + aboutWebhooks: "More info about Webhooks on: {0}", + aboutChannelName: "Enter the channel name on {0} Channel Name field if you want to bypass the Webhook channel. Ex: #other-channel", + aboutKumaURL: "If you leave the Uptime Kuma URL field blank, it will default to the Project GitHub page.", + emojiCheatSheet: "Emoji cheat sheet: {0}", + "User Key": "User Key", + Device: "Device", + "Message Title": "Message Title", + "Notification Sound": "Notification Sound", + "More info on:": "More info on: {0}", + pushoverDesc1: "Emergency priority (2) has default 30 second timeout between retries and will expire after 1 hour.", + pushoverDesc2: "If you want to send notifications to different devices, fill out Device field.", + "SMS Type": "SMS Type", + octopushTypePremium: "Premium (Fast - recommended for alerting)", + octopushTypeLowCost: "Low Cost (Slow - sometimes blocked by operator)", + checkPrice: "Check {0} prices:", + octopushLegacyHint: "Do you use the legacy version of Octopush (2011-2020) or the new version?", + "Check octopush prices": "Check octopush prices {0}.", + octopushPhoneNumber: "Phone number (intl format, eg : +33612345678) ", + octopushSMSSender: "SMS Sender Name : 3-11 alphanumeric characters and space (a-zA-Z0-9)", + "LunaSea Device ID": "LunaSea Device ID", + "Apprise URL": "Apprise URL", + "Example:": "Example: {0}", + "Read more:": "Read more: {0}", + "Status:": "Status: {0}", + "Read more": "Read more", + appriseInstalled: "Apprise is installed.", + appriseNotInstalled: "Apprise is not installed. {0}", + "Access Token": "Access Token", + "Channel access token": "Channel access token", + "Line Developers Console": "Line Developers Console", + lineDevConsoleTo: "Line Developers Console - {0}", + "Basic Settings": "Basic Settings", + "User ID": "User ID", + "Messaging API": "Messaging API", + wayToGetLineChannelToken: "First access the {0}, create a provider and channel (Messaging API), then you can get the channel access token and user ID from the above mentioned menu items.", + "Icon URL": "Icon URL", + aboutIconURL: "You can provide a link to a picture in \"Icon URL\" to override the default profile picture. Will not be used if Icon Emoji is set.", + aboutMattermostChannelName: "You can override the default channel that the Webhook posts to by entering the channel name into \"Channel Name\" field. This needs to be enabled in the Mattermost Webhook settings. Ex: #other-channel", + matrix: "Matrix", + promosmsTypeEco: "SMS ECO - cheap but slow and often overloaded. Limited only to Polish recipients.", + promosmsTypeFlash: "SMS FLASH - Message will automatically show on recipient device. Limited only to Polish recipients.", + promosmsTypeFull: "SMS FULL - Premium tier of SMS, You can use your Sender Name (You need to register name first). Reliable for alerts.", + promosmsTypeSpeed: "SMS SPEED - Highest priority in system. Very quick and reliable but costly (about twice of SMS FULL price).", + promosmsPhoneNumber: "Phone number (for Polish recipient You can skip area codes)", + promosmsSMSSender: "SMS Sender Name : Pre-registred name or one of defaults: InfoSMS, SMS Info, MaxSMS, INFO, SMS", + "Feishu WebHookUrl": "Feishu WebHookURL", + matrixHomeserverURL: "Homeserver URL (with http(s):// and optionally port)", + "Internal Room Id": "Internal Room ID", + matrixDesc1: "You can find the internal room ID by looking in the advanced section of the room settings in your Matrix client. It should look like !QMdRCpUIfLwsfjxye6:home.server.", + matrixDesc2: "It is highly recommended you create a new user and do not use your own Matrix user's access token as it will allow full access to your account and all the rooms you joined. Instead, create a new user and only invite it to the room that you want to receive the notification in. You can get the access token by running {0}", + Method: "Метод", + Body: "Тело", + Headers: "Заголовки", + PushUrl: "URL пуша", + HeadersInvalidFormat: "Заголовки запроса некорректны JSON: ", + BodyInvalidFormat: "Тело запроса некорректно JSON: ", + "Monitor History": "История мониторов", + clearDataOlderThan: "Сохранять историю мониторов в течение {0} дней.", + PasswordsDoNotMatch: "Пароли не совпадают.", + records: "записей", + "One record": "Одна запись", + "Showing {from} to {to} of {count} records": "Показывается от {from} до {to} из {count} записей", + steamApiKeyDescription: "Для мониторинга игрового сервера Steam вам необходим Web-API ключ Steam. Зарегистрировать его можно здесь: ", }; From c0d6fe0d768cf1ddc52f11c1d95d77463685ae0e Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Thu, 21 Oct 2021 00:03:55 +0800 Subject: [PATCH 36/41] Update CONTRIBUTING.md --- CONTRIBUTING.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9c4d5dc4..45996f4a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -178,3 +178,21 @@ Patch release = the third digit ([Semantic Versioning](https://semver.org/)) ## Translations Please read: https://github.com/louislam/uptime-kuma/tree/master/src/languages + + +## Maintainer + +Check the latest issue and pull request: +https://github.com/louislam/uptime-kuma/issues?q=sort%3Aupdated-desc + +### Release Procedures +1. Draft a release note +1. Make sure the repo is cleared +1. `npm run update-version 1.X.X` +1. `npm run build-docker` +1. git push +1. Publish the release note as 1.X.X +1. npm run upload-artifacts +1. Try the Docker image with tag 1.X.X (Clean install / amd64 / arm64 / armv7) +1. Try clean install with Node.js +1. SSH to demo site server and update to 1.X.X From 8f1da6aa2285027f8d19c99abc2d8a47f75b0ac2 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Thu, 21 Oct 2021 00:08:46 +0800 Subject: [PATCH 37/41] Update CONTRIBUTING.md --- CONTRIBUTING.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 45996f4a..1d9b37a3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -182,7 +182,7 @@ Please read: https://github.com/louislam/uptime-kuma/tree/master/src/languages ## Maintainer -Check the latest issue and pull request: +Check the latest issues and pull requests: https://github.com/louislam/uptime-kuma/issues?q=sort%3Aupdated-desc ### Release Procedures @@ -193,6 +193,9 @@ https://github.com/louislam/uptime-kuma/issues?q=sort%3Aupdated-desc 1. git push 1. Publish the release note as 1.X.X 1. npm run upload-artifacts -1. Try the Docker image with tag 1.X.X (Clean install / amd64 / arm64 / armv7) -1. Try clean install with Node.js 1. SSH to demo site server and update to 1.X.X + +Checking: +- Check all tags is fine on https://hub.docker.com/r/louislam/uptime-kuma/tags +- Try the Docker image with tag 1.X.X (Clean install / amd64 / arm64 / armv7) +- Try clean install with Node.js From 56094a43d7a50b4c1b0900f566378cbc71cd147c Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Thu, 21 Oct 2021 22:54:04 +0800 Subject: [PATCH 38/41] add passwordStrength --- package-lock.json | 15 +++++++++++++-- package.json | 3 ++- server/server.js | 11 ++++++++++- 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 22b45db4..ea2458ac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "uptime-kuma", - "version": "1.8.0", + "version": "1.9.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "uptime-kuma", - "version": "1.8.0", + "version": "1.9.1", "license": "MIT", "dependencies": { "@fortawesome/fontawesome-svg-core": "~1.2.36", @@ -23,6 +23,7 @@ "chardet": "^1.3.0", "chart.js": "~3.5.1", "chartjs-adapter-dayjs": "~1.0.0", + "check-password-strength": "^2.0.3", "command-exists": "~1.2.9", "compare-versions": "~3.6.0", "dayjs": "~1.10.7", @@ -3905,6 +3906,11 @@ "dayjs": "^1.8.15" } }, + "node_modules/check-password-strength": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/check-password-strength/-/check-password-strength-2.0.3.tgz", + "integrity": "sha512-UW3YgMUne9QuejgnNWjWwYi4QhWArVj+1OXqDR1NkEQcmMKKO74O3P5ZvXr9JZNbTBfcwlK3yurYCMuJsck83A==" + }, "node_modules/chokidar": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", @@ -15474,6 +15480,11 @@ "integrity": "sha512-EnbVqTJGFKLpg1TROLdCEufrzbmIa2oeLGx8O2Wdjw2EoMudoOo9+YFu+6CM0Z0hQ/v3yq/e/Y6efQMu22n8Jg==", "requires": {} }, + "check-password-strength": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/check-password-strength/-/check-password-strength-2.0.3.tgz", + "integrity": "sha512-UW3YgMUne9QuejgnNWjWwYi4QhWArVj+1OXqDR1NkEQcmMKKO74O3P5ZvXr9JZNbTBfcwlK3yurYCMuJsck83A==" + }, "chokidar": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", diff --git a/package.json b/package.json index 174ab6aa..6f4bef34 100644 --- a/package.json +++ b/package.json @@ -62,10 +62,11 @@ "axios": "~0.21.4", "bcryptjs": "~2.4.3", "bootstrap": "~5.1.1", - "chardet": "^1.3.0", "bree": "~6.3.1", + "chardet": "^1.3.0", "chart.js": "~3.5.1", "chartjs-adapter-dayjs": "~1.0.0", + "check-password-strength": "^2.0.3", "command-exists": "~1.2.9", "compare-versions": "~3.6.0", "dayjs": "~1.10.7", diff --git a/server/server.js b/server/server.js index 1846fe6e..11f03061 100644 --- a/server/server.js +++ b/server/server.js @@ -31,6 +31,7 @@ debug("Importing prometheus-api-metrics"); const prometheusAPIMetrics = require("prometheus-api-metrics"); debug("Importing compare-versions"); const compareVersions = require("compare-versions"); +const { passwordStrength } = require("check-password-strength"); debug("Importing 2FA Modules"); const notp = require("notp"); @@ -468,6 +469,10 @@ exports.entryPage = "dashboard"; socket.on("setup", async (username, password, callback) => { try { + if (passwordStrength(password).value === "Too weak") { + throw new Error("Password is too weak. It should contain alphabetic and numeric characters. It must be at least 6 characters in length."); + } + if ((await R.count("user")) !== 0) { throw new Error("Uptime Kuma has been initialized. If you want to run setup again, please delete the database."); } @@ -857,10 +862,14 @@ exports.entryPage = "dashboard"; try { checkLogin(socket); - if (! password.currentPassword) { + if (! password.newPassword) { throw new Error("Invalid new password"); } + if (passwordStrength(password.newPassword).value === "Too weak") { + throw new Error("Password is too weak. It should contain alphabetic and numeric characters. It must be at least 6 characters in length."); + } + let user = await R.findOne("user", " id = ? AND active = 1 ", [ socket.userID, ]); From ff5f2e8dfb0822ba3642ed94ad8ff81da3256902 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Thu, 21 Oct 2021 23:03:40 +0800 Subject: [PATCH 39/41] add limiter --- package-lock.json | 27 +++++++++++++++++++++++++++ package.json | 1 + 2 files changed, 28 insertions(+) diff --git a/package-lock.json b/package-lock.json index ea2458ac..551504d7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,6 +33,7 @@ "http-graceful-shutdown": "~3.1.4", "iconv-lite": "^0.6.3", "jsonwebtoken": "~8.5.1", + "limiter": "^2.1.0", "nodemailer": "~6.6.5", "notp": "~2.0.3", "password-hash": "~1.2.2", @@ -7821,6 +7822,11 @@ "verror": "1.10.0" } }, + "node_modules/just-performance": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/just-performance/-/just-performance-4.3.0.tgz", + "integrity": "sha512-L7RjvtJsL0QO8xFs5wEoDDzzJwoiowRw6Rn/GnvldlchS2JQr9wFYPiwZcDfrbbujEKqKN0tvENdbjXdYhDp5Q==" + }, "node_modules/jwa": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", @@ -7957,6 +7963,14 @@ "node": ">= 0.8.0" } }, + "node_modules/limiter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/limiter/-/limiter-2.1.0.tgz", + "integrity": "sha512-361TYz6iay6n+9KvUUImqdLuFigK+K79qrUtBsXhJTLdH4rIt/r1y8r1iozwh8KbZNpujbFTSh74mJ7bwbAMOw==", + "dependencies": { + "just-performance": "4.3.0" + } + }, "node_modules/lines-and-columns": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", @@ -18457,6 +18471,11 @@ "verror": "1.10.0" } }, + "just-performance": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/just-performance/-/just-performance-4.3.0.tgz", + "integrity": "sha512-L7RjvtJsL0QO8xFs5wEoDDzzJwoiowRw6Rn/GnvldlchS2JQr9wFYPiwZcDfrbbujEKqKN0tvENdbjXdYhDp5Q==" + }, "jwa": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", @@ -18551,6 +18570,14 @@ "type-check": "~0.4.0" } }, + "limiter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/limiter/-/limiter-2.1.0.tgz", + "integrity": "sha512-361TYz6iay6n+9KvUUImqdLuFigK+K79qrUtBsXhJTLdH4rIt/r1y8r1iozwh8KbZNpujbFTSh74mJ7bwbAMOw==", + "requires": { + "just-performance": "4.3.0" + } + }, "lines-and-columns": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", diff --git a/package.json b/package.json index 6f4bef34..4f3d32ea 100644 --- a/package.json +++ b/package.json @@ -76,6 +76,7 @@ "http-graceful-shutdown": "~3.1.4", "iconv-lite": "^0.6.3", "jsonwebtoken": "~8.5.1", + "limiter": "^2.1.0", "nodemailer": "~6.6.5", "notp": "~2.0.3", "password-hash": "~1.2.2", From 1d1601cf24b4ee721b123ef594bd2571fa5b75aa Mon Sep 17 00:00:00 2001 From: DX37 Date: Thu, 21 Oct 2021 22:49:17 +0700 Subject: [PATCH 40/41] Update notifications area in ru-RU.js --- src/languages/ru-RU.js | 180 ++++++++++++++++++++--------------------- 1 file changed, 90 insertions(+), 90 deletions(-) diff --git a/src/languages/ru-RU.js b/src/languages/ru-RU.js index 95146239..bf4d3086 100644 --- a/src/languages/ru-RU.js +++ b/src/languages/ru-RU.js @@ -185,7 +185,7 @@ export default { "Switch to Dark Theme": "Тёмная тема", "Switch to Light Theme": "Светлая тема", telegram: "Telegram", - webhook: "Webhook", + webhook: "Вебхук", smtp: "Email (SMTP)", discord: "Discord", teams: "Microsoft Teams", @@ -198,104 +198,104 @@ export default { octopush: "Octopush", promosms: "PromoSMS", lunasea: "LunaSea", - apprise: "Apprise (Support 50+ Notification services)", + apprise: "Apprise (Поддержка 50+ сервисов уведомлений)", pushbullet: "Pushbullet", line: "Line Messenger", mattermost: "Mattermost", - "Primary Base URL": "Primary Base URL", - "Push URL": "Push URL", - needPushEvery: "You should call this URL every {0} seconds.", - pushOptionalParams: "Optional parameters: {0}", - defaultNotificationName: "My {notification} Alert ({number})", - here: "here", - Required: "Required", - "Bot Token": "Bot Token", - wayToGetTelegramToken: "You can get a token from {0}.", - "Chat ID": "Chat ID", - supportTelegramChatID: "Support Direct Chat / Group / Channel's Chat ID", - wayToGetTelegramChatID: "You can get your chat ID by sending a message to the bot and going to this URL to view the chat_id:", - "YOUR BOT TOKEN HERE": "YOUR BOT TOKEN HERE", - chatIDNotFound: "Chat ID is not found; please send a message to this bot first", + "Primary Base URL": "Основной URL", + "Push URL": "URL пуша", + needPushEvery: "К этому URL необходимо обращаться каждые {0} секунд.", + pushOptionalParams: "Опциональные параметры: {0}", + defaultNotificationName: "Моё уведомление {notification} ({number})", + here: "здесь", + Required: "Требуется", + "Bot Token": "Токен бота", + wayToGetTelegramToken: "Вы можете взять токен здесь - {0}.", + "Chat ID": "ID чата", + supportTelegramChatID: "Поддерживаются ID чатов, групп и каналов", + wayToGetTelegramChatID: "Вы можете взять ID вашего чата, отправив сообщение боту и перейдя по этому URL для просмотра chat_id:", + "YOUR BOT TOKEN HERE": "ВАШ ТОКЕН БОТА ЗДЕСЬ", + chatIDNotFound: "ID чата не найден; пожалуйста отправьте сначала сообщение боту", "Post URL": "Post URL", - "Content Type": "Content Type", - webhookJsonDesc: "{0} is good for any modern HTTP servers such as Express.js", - webhookFormDataDesc: "{multipart} is good for PHP. The JSON will need to be parsed with {decodeFunction}", - secureOptionNone: "None / STARTTLS (25, 587)", + "Content Type": "Тип контента", + webhookJsonDesc: "{0} подходит для любых современных HTTP-серверов, например Express.js", + webhookFormDataDesc: "{multipart} подходит для PHP. JSON-вывод необходимо будет обработать с помощью {decodeFunction}", + secureOptionNone: "Нет / STARTTLS (25, 587)", secureOptionTLS: "TLS (465)", - "Ignore TLS Error": "Ignore TLS Error", - "From Email": "From Email", - emailCustomSubject: "Custom Subject", - "To Email": "To Email", - smtpCC: "CC", - smtpBCC: "BCC", - "Discord Webhook URL": "Discord Webhook URL", - wayToGetDiscordURL: "You can get this by going to Server Settings -> Integrations -> Create Webhook", - "Bot Display Name": "Bot Display Name", - "Prefix Custom Message": "Prefix Custom Message", - "Hello @everyone is...": "Hello {'@'}everyone is...", - "Webhook URL": "Webhook URL", - wayToGetTeamsURL: "You can learn how to create a webhook URL {0}.", - Number: "Number", - Recipients: "Recipients", - needSignalAPI: "You need to have a signal client with REST API.", - wayToCheckSignalURL: "You can check this URL to view how to set one up:", - signalImportant: "IMPORTANT: You cannot mix groups and numbers in recipients!", - "Application Token": "Application Token", - "Server URL": "Server URL", - Priority: "Priority", - "Icon Emoji": "Icon Emoji", - "Channel Name": "Channel Name", + "Ignore TLS Error": "Игнорировать ошибки TLS", + "From Email": "От кого", + emailCustomSubject: "Своя тема", + "To Email": "Кому", + smtpCC: "Копия", + smtpBCC: "Скрытая копия", + "Discord Webhook URL": "Discord вебхук URL", + wayToGetDiscordURL: "Вы можете создать его в Параметрах сервера -> Интеграции -> Создать вебхук", + "Bot Display Name": "Отображаемое имя бота", + "Prefix Custom Message": "Свой префикс сообщения", + "Hello @everyone is...": "Привет {'@'}everyone это...", + "Webhook URL": "URL вебхука", + wayToGetTeamsURL: "Как создать URL вебхука вы можете узнать здесь - {0}.", + Number: "Номер", + Recipients: "Получатели", + needSignalAPI: "Вам необходим клиент Signal с поддержкой REST API.", + wayToCheckSignalURL: "Пройдите по этому URL, чтобы узнать как настроить такой клиент:", + signalImportant: "ВАЖНО: Нельзя смешивать в Получателях группы и номера!", + "Application Token": "Токен приложения", + "Server URL": "URL сервера", + Priority: "Приоритет", + "Icon Emoji": "Иконка Emoji", + "Channel Name": "Имя канала", "Uptime Kuma URL": "Uptime Kuma URL", - aboutWebhooks: "More info about Webhooks on: {0}", - aboutChannelName: "Enter the channel name on {0} Channel Name field if you want to bypass the Webhook channel. Ex: #other-channel", - aboutKumaURL: "If you leave the Uptime Kuma URL field blank, it will default to the Project GitHub page.", - emojiCheatSheet: "Emoji cheat sheet: {0}", - "User Key": "User Key", - Device: "Device", - "Message Title": "Message Title", - "Notification Sound": "Notification Sound", - "More info on:": "More info on: {0}", - pushoverDesc1: "Emergency priority (2) has default 30 second timeout between retries and will expire after 1 hour.", - pushoverDesc2: "If you want to send notifications to different devices, fill out Device field.", - "SMS Type": "SMS Type", - octopushTypePremium: "Premium (Fast - recommended for alerting)", - octopushTypeLowCost: "Low Cost (Slow - sometimes blocked by operator)", - checkPrice: "Check {0} prices:", - octopushLegacyHint: "Do you use the legacy version of Octopush (2011-2020) or the new version?", - "Check octopush prices": "Check octopush prices {0}.", - octopushPhoneNumber: "Phone number (intl format, eg : +33612345678) ", - octopushSMSSender: "SMS Sender Name : 3-11 alphanumeric characters and space (a-zA-Z0-9)", - "LunaSea Device ID": "LunaSea Device ID", + aboutWebhooks: "Больше информации о вебхуках: {0}", + aboutChannelName: "Введите имя канала в поле {0} Имя канала, если вы хотите обойти канал вебхука. Например: #other-channel", + aboutKumaURL: "Если поле Uptime Kuma URL в настройках останется пустым, по умолчанию будет использоваться ссылка на проект на GitHub.", + emojiCheatSheet: "Шпаргалка по Emoji: {0}", + "User Key": "Ключ пользователя", + Device: "Устройство", + "Message Title": "Заголовок сообщения", + "Notification Sound": "Звук уведомления", + "More info on:": "Больше информации: {0}", + pushoverDesc1: "Экстренный приоритет (2) имеет таймаут повтора по умолчанию 30 секунд и истекает через 1 час.", + pushoverDesc2: "Если вы хотите отправлять уведомления различным устройствам, необходимо заполнить поле Устройство.", + "SMS Type": "Тип SMS", + octopushTypePremium: "Премиум (Быстрый - рекомендуется для алертов)", + octopushTypeLowCost: "Дешёвый (Медленный - иногда блокируется операторами)", + checkPrice: "Тарифы {0}:", + octopushLegacyHint: "Вы используете старую версию Octopush (2011-2020) или новую?", + "Check octopush prices": "Тарифы Octopush {0}.", + octopushPhoneNumber: "Номер телефона (межд. формат, например: +79831234567) ", + octopushSMSSender: "Имя отправителя SMS: 3-11 символов алвафита, цифр и пробелов (a-zA-Z0-9)", + "LunaSea Device ID": "ID устройства LunaSea", "Apprise URL": "Apprise URL", - "Example:": "Example: {0}", - "Read more:": "Read more: {0}", - "Status:": "Status: {0}", - "Read more": "Read more", - appriseInstalled: "Apprise is installed.", - appriseNotInstalled: "Apprise is not installed. {0}", - "Access Token": "Access Token", - "Channel access token": "Channel access token", - "Line Developers Console": "Line Developers Console", - lineDevConsoleTo: "Line Developers Console - {0}", - "Basic Settings": "Basic Settings", - "User ID": "User ID", - "Messaging API": "Messaging API", - wayToGetLineChannelToken: "First access the {0}, create a provider and channel (Messaging API), then you can get the channel access token and user ID from the above mentioned menu items.", - "Icon URL": "Icon URL", - aboutIconURL: "You can provide a link to a picture in \"Icon URL\" to override the default profile picture. Will not be used if Icon Emoji is set.", - aboutMattermostChannelName: "You can override the default channel that the Webhook posts to by entering the channel name into \"Channel Name\" field. This needs to be enabled in the Mattermost Webhook settings. Ex: #other-channel", + "Example:": "Пример: {0}", + "Read more:": "Подробнее: {0}", + "Status:": "Статус: {0}", + "Read more": "Подробнее", + appriseInstalled: "Apprise установлен.", + appriseNotInstalled: "Apprise не установлен. {0}", + "Access Token": "Токен доступа", + "Channel access token": "Токен доступа канала", + "Line Developers Console": "Консоль разработчиков Line", + lineDevConsoleTo: "Консоль разработчиков Line - {0}", + "Basic Settings": "Базовые настройки", + "User ID": "ID пользователя", + "Messaging API": "API сообщений", + wayToGetLineChannelToken: "Сначала зайдите в {0}, создайте провайдера и канал (API сообщений), затем вы сможете получить токен доступа канала и ID пользователя из вышеупомянутых пунктов меню.", + "Icon URL": "URL иконки", + aboutIconURL: "Вы можете предоставить ссылку на иконку в поле \"URL иконки\" чтобы переопределить картинку профиля по умолчанию. Не используется, если задана иконка Emoji.", + aboutMattermostChannelName: "Вы можете переопределить канал по умолчанию, в который вебхук пишет, введя имя канала в поле \"Имя канала\". Это необходимо включить в настройках вебхука Mattermost. Например: #other-channel", matrix: "Matrix", - promosmsTypeEco: "SMS ECO - cheap but slow and often overloaded. Limited only to Polish recipients.", - promosmsTypeFlash: "SMS FLASH - Message will automatically show on recipient device. Limited only to Polish recipients.", - promosmsTypeFull: "SMS FULL - Premium tier of SMS, You can use your Sender Name (You need to register name first). Reliable for alerts.", - promosmsTypeSpeed: "SMS SPEED - Highest priority in system. Very quick and reliable but costly (about twice of SMS FULL price).", - promosmsPhoneNumber: "Phone number (for Polish recipient You can skip area codes)", - promosmsSMSSender: "SMS Sender Name : Pre-registred name or one of defaults: InfoSMS, SMS Info, MaxSMS, INFO, SMS", + promosmsTypeEco: "SMS ECO - дёшево и медленно, часто перегружен. Только для получателей из Польши.", + promosmsTypeFlash: "SMS FLASH - сообщения автоматически появятся на устройстве получателя. Только для получателей из Польши.", + promosmsTypeFull: "SMS FULL - премиум-уровень SMS, можно использовать своё имя отправителя (предварительно зарегистрировав его). Надёжно для алертов.", + promosmsTypeSpeed: "SMS SPEED - наивысший приоритет в системе. Очень быстро и надёжно, но очень дорого (в два раза дороже, чем SMS FULL).", + promosmsPhoneNumber: "Номер телефона (для получателей из Польши можно пропустить код региона)", + promosmsSMSSender: "Имя отправителя SMS: Зарегистрированное или одно из имён по умолчанию: InfoSMS, SMS Info, MaxSMS, INFO, SMS", "Feishu WebHookUrl": "Feishu WebHookURL", - matrixHomeserverURL: "Homeserver URL (with http(s):// and optionally port)", - "Internal Room Id": "Internal Room ID", - matrixDesc1: "You can find the internal room ID by looking in the advanced section of the room settings in your Matrix client. It should look like !QMdRCpUIfLwsfjxye6:home.server.", - matrixDesc2: "It is highly recommended you create a new user and do not use your own Matrix user's access token as it will allow full access to your account and all the rooms you joined. Instead, create a new user and only invite it to the room that you want to receive the notification in. You can get the access token by running {0}", + matrixHomeserverURL: "URL сервера (вместе с http(s):// и опционально порт)", + "Internal Room Id": "Внутренний ID комнаты", + matrixDesc1: "Внутренний ID комнаты можно найти в Подробностях в параметрах канала вашего Matrix клиента. Он должен выглядеть примерно как !QMdRCpUIfLwsfjxye6:home.server.", + matrixDesc2: "Рекомендуется создать нового пользователя и не использовать токен доступа личного пользователя Matrix, т.к. это влечёт за собой полный доступ к аккаунту и к комнатам, в которых вы состоите. Вместо этого создайте нового пользователя и пригласите его только в ту комнату, в которой вы хотите получать уведомления. Токен доступа можно получить, выполнив команду {0}", Method: "Метод", Body: "Тело", Headers: "Заголовки", From 657987a0132ce7074de3034d2b12843eed2be545 Mon Sep 17 00:00:00 2001 From: Andreas Brett Date: Fri, 22 Oct 2021 11:59:53 +0200 Subject: [PATCH 41/41] fix: show beat.msg only if available --- src/components/HeartbeatBar.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/HeartbeatBar.vue b/src/components/HeartbeatBar.vue index e62b95df..be0b122e 100644 --- a/src/components/HeartbeatBar.vue +++ b/src/components/HeartbeatBar.vue @@ -167,7 +167,7 @@ export default { }, getBeatTitle(beat) { - return `${this.$root.datetime(beat.time)} - ${beat.msg}`; + return `${this.$root.datetime(beat.time)}` + ((beat.msg) ? ` - ${beat.msg}` : ``); } }, }