From 251d42f1a6f4a295dd69f1e5b77d6a9ab33f1b59 Mon Sep 17 00:00:00 2001 From: Soroosh Date: Sun, 26 Sep 2021 01:09:00 +0330 Subject: [PATCH 01/23] Add localeDirection method to i18n.js Add dir to html tag based on localeDirection Add Farsi to the languages --- src/App.vue | 22 +++++- src/i18n.js | 13 +++- src/languages/fa.js | 172 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 204 insertions(+), 3 deletions(-) create mode 100644 src/languages/fa.js diff --git a/src/App.vue b/src/App.vue index a16d4208..0930852e 100644 --- a/src/App.vue +++ b/src/App.vue @@ -3,5 +3,25 @@ diff --git a/src/i18n.js b/src/i18n.js index fe2612fb..14df6aa4 100644 --- a/src/i18n.js +++ b/src/i18n.js @@ -2,6 +2,7 @@ import { createI18n } from "vue-i18n"; import daDK from "./languages/da-DK"; import deDE from "./languages/de-DE"; import en from "./languages/en"; +import fa from "./languages/fa"; import esEs from "./languages/es-ES"; import etEE from "./languages/et-EE"; import frFR from "./languages/fr-FR"; @@ -24,6 +25,7 @@ const languageList = { "de-DE": deDE, "nl-NL": nlNL, "es-ES": esEs, + "fa": fa, "fr-FR": frFR, "it-IT": itIT, "ja": ja, @@ -39,10 +41,17 @@ const languageList = { "et-EE": etEE, }; +const rtlLangs = ["fa"]; + +const currentLocale = () => localStorage.locale || "en"; + +export const localeDirection = () => { + return rtlLangs.includes(currentLocale()) ? "rtl" : "ltr" +} export const i18n = createI18n({ - locale: localStorage.locale || "en", + locale: currentLocale(), fallbackLocale: "en", silentFallbackWarn: true, silentTranslationWarn: false, - messages: languageList, + messages: languageList }); diff --git a/src/languages/fa.js b/src/languages/fa.js new file mode 100644 index 00000000..436208e7 --- /dev/null +++ b/src/languages/fa.js @@ -0,0 +1,172 @@ +export default { + languageName: "Farsi", + checkEverySecond: "بررسی هر {0} ثانیه.", + retryCheckEverySecond: "تکرار مجدد هر {0} ثانیه.", + retriesDescription: "حداکثر تعداد تکرار پیش از علامت گذاری وب‌سایت بعنوان خارج از دسترس و ارسال اطلاع‌رسانی.", + ignoreTLSError: "بی‌خیال ارور TLS/SSL برای سایت‌های HTTPS", + 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", + pauseDashboardHome: "متوقف شده", + 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", + pauseMonitorMsg: "Are you sure want to pause?", + enableDefaultNotificationDescription: "For every new monitor this notification will be enabled by default. You can still disable the notification separately for each monitor.", + clearEventsMsg: "Are you sure want to delete all events for this monitor?", + clearHeartbeatsMsg: "Are you sure want to delete all heartbeats for this monitor?", + confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?", + importHandleDescription: "Choose 'Skip existing' if you want to skip every monitor or notification with the same name. 'Overwrite' will delete every existing monitor and notification.", + confirmImportMsg: "Are you sure to import the backup? Please make sure you've selected the right import option.", + twoFAVerifyLabel: "Please type in your token to verify that 2FA is working", + tokenValidSettingsMsg: "Token is valid! You can now save the 2FA settings.", + confirmEnableTwoFAMsg: "Are you sure you want to enable 2FA?", + confirmDisableTwoFAMsg: "Are you sure you want to disable 2FA?", + Settings: "تنظیمات", + Dashboard: "پیشخوان", + "New Update": "بروزرسانی جدید!", + Language: "زبان", + Appearance: "ظاهر", + Theme: "پوسته", + General: "عمومی", + Version: "نسخه", + "Check Update On GitHub": "بررسی بروز رسانی بر روی گیت‌هاب", + List: "لیست", + Add: "اضافه", + "Add New Monitor": "اضافه کردن مانیتور جدید", + "Quick Stats": "خلاصه وضعیت", + Up: "فعال", + Down: "غیرفعال", + Pending: "در انتظار تایید", + Unknown: "نامشخص", + Pause: "توقف", + Name: "نام", + Status: "وضعیت", + DateTime: "تاریخ و زمان", + Message: "پیام", + "No important events": "رخداد جدید نیست.", + Resume: "ادامه", + Edit: "ویرایش", + Delete: "حذف", + Current: "فعلی", + Uptime: "آپتایم", + "Cert Exp.": "تاریخ انقضای SSL", + days: "روز", + day: "روز", + "-day": "-روز", + hour: "ساعت", + "-hour": "-ساعت", + Response: "پاسخ", + Ping: "Ping", + "Monitor Type": "نوع مانیتور", + Keyword: "کلمه کلیدی", + "Friendly Name": "عنوان", + URL: "URL", + Hostname: "نام میزبان (Hostname)", + Port: "پورت", + "Heartbeat Interval": "فاصله هر Heartbeat", + Retries: "تلاش مجدد", + "Heartbeat Retry Interval": "فاصله تلاش مجدد برایHeartbeat", + Advanced: "پیشرفته", + "Upside Down Mode": "حالت بر عکس", + "Max. Redirects": "حداکثر تعداد ری‌دایرکت", + "Accepted Status Codes": "وضعیت‌های (Status Code) های قابل قبول", + Save: "ذخیره", + Notifications: "اطلاع‌رسانی‌ها", + "Not available, please setup.": "هیچ موردی موجود نیست، اولین مورد را راه اندازی کنید!", + "Setup Notification": "راه اندازی اطلاع‌رسانی‌", + Light: "روشن", + Dark: "تاریک", + Auto: "اتوماتیک", + "Theme - Heartbeat Bar": "Theme - Heartbeat Bar", + Normal: "Normal", + Bottom: "Bottom", + None: "None", + Timezone: "موقعیت زمانی", + "Search Engine Visibility": "Search Engine Visibility", + "Allow indexing": "Allow indexing", + "Discourage search engines from indexing site": "Discourage search engines from indexing site", + "Change Password": "Change Password", + "Current Password": "Current Password", + "New Password": "New Password", + "Repeat New Password": "Repeat New Password", + "Update Password": "Update Password", + "Disable Auth": "Disable Auth", + "Enable Auth": "Enable Auth", + Logout: "خروج", + Leave: "Leave", + "I understand, please disable": "متوجه هست»، لطفا غیرفعال کنید!", + Confirm: "تایید", + Yes: "بلی", + No: "خیر", + Username: "نام کاربری", + Password: "کلمه عبور", + "Remember me": "مراب هب خاطر بسپار", + Login: "ورود", + "No Monitors, please": "No Monitors, please", + "add one": "add one", + "Notification Type": "Notification Type", + Email: "Email", + Test: "Test", + "Certificate Info": "Certificate Info", + "Resolver Server": "Resolver Server", + "Resource Record Type": "Resource Record Type", + "Last Result": "Last Result", + "Create your admin account": "Create your admin account", + "Repeat Password": "Repeat Password", + "Import Backup": "Import Backup", + "Export Backup": "Export Backup", + Export: "Export", + Import: "Import", + respTime: "Resp. Time (ms)", + notAvailableShort: "N/A", + "Default enabled": "Default enabled", + "Apply on all existing monitors": "Apply on all existing monitors", + Create: "Create", + "Clear Data": "Clear Data", + 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.", + alertNoFile: "Please select a file to import.", + alertWrongFileType: "Please select a JSON file.", + "Clear all statistics": "Clear all Statistics", + "Skip existing": "Skip existing", + Overwrite: "Overwrite", + Options: "Options", + "Keep both": "Keep both", + "Verify Token": "Verify Token", + "Setup 2FA": "Setup 2FA", + "Enable 2FA": "Enable 2FA", + "Disable 2FA": "Disable 2FA", + "2FA Settings": "2FA Settings", + "Two Factor Authentication": "Two Factor Authentication", + Active: "Active", + Inactive: "Inactive", + Token: "Token", + "Show URI": "Show URI", + Tags: "Tags", + "Add New below or Select...": "Add New below or Select...", + "Tag with this name already exist.": "Tag with this name already exist.", + "Tag with this value already exist.": "Tag with this value already exist.", + color: "color", + "value (optional)": "value (optional)", + Gray: "Gray", + Red: "Red", + Orange: "Orange", + Green: "Green", + Blue: "Blue", + Indigo: "Indigo", + Purple: "Purple", + Pink: "Pink", + "Search...": "Search...", + "Avg. Ping": "Avg. Ping", + "Avg. Response": "Avg. Response", + "Uptime Kuma": "آپتایم کوما" +} From e60426bdcdb647cb108449d3f0819282a7d68e2b Mon Sep 17 00:00:00 2001 From: Soroosh Date: Sun, 26 Sep 2021 18:49:03 +0330 Subject: [PATCH 02/23] Add Localization CSS & Persian Font --- src/assets/app.scss | 5 +++++ src/assets/localization.scss | 7 +++++++ 2 files changed, 12 insertions(+) create mode 100644 src/assets/localization.scss diff --git a/src/assets/app.scss b/src/assets/app.scss index 58164573..f553ccf3 100644 --- a/src/assets/app.scss +++ b/src/assets/app.scss @@ -288,3 +288,8 @@ h2 { transform: translateY(50px); opacity: 0; } + + +// Localization + +@import "localization.scss"; \ No newline at end of file diff --git a/src/assets/localization.scss b/src/assets/localization.scss new file mode 100644 index 00000000..c7eb9040 --- /dev/null +++ b/src/assets/localization.scss @@ -0,0 +1,7 @@ +@import url('http://fonts.cdnfonts.com/css/iranian-sans'); + +html[lang='fa'] { + #app { + font-family: 'Iranian Sans', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, segoe ui, Roboto, helvetica neue, Arial, noto sans, sans-serif, apple color emoji, segoe ui emoji, segoe ui symbol, noto color emoji; + } +} \ No newline at end of file From 647184e5d1400935b05d877f44de2650980b6fab Mon Sep 17 00:00:00 2001 From: Soroosh Date: Sun, 26 Sep 2021 18:49:39 +0330 Subject: [PATCH 03/23] Update Title to use translation files --- src/layouts/Layout.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/layouts/Layout.vue b/src/layouts/Layout.vue index 467ae53a..d28f354c 100644 --- a/src/layouts/Layout.vue +++ b/src/layouts/Layout.vue @@ -10,7 +10,7 @@
- Uptime Kuma + {{ $t("Uptime Kuma") }} From f41e95921f5adff22bb9bc1231d13c508746914f Mon Sep 17 00:00:00 2001 From: Soroosh Date: Sun, 26 Sep 2021 18:50:12 +0330 Subject: [PATCH 04/23] Enable localization for pagination --- src/pages/DashboardHome.vue | 12 ++++++++++++ src/pages/Details.vue | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/src/pages/DashboardHome.vue b/src/pages/DashboardHome.vue index 186184ec..9a9e2a82 100644 --- a/src/pages/DashboardHome.vue +++ b/src/pages/DashboardHome.vue @@ -57,6 +57,7 @@ v-model="page" :records="importantHeartBeatList.length" :per-page="perPage" + :options="paginationConfig" /> @@ -81,6 +82,17 @@ export default { page: 1, perPage: 25, heartBeatList: [], + paginationConfig: { + texts:{ + count:`${this.$t("Showing {from} to {to} of {count} records")}|{count} ${this.$t("records")}|${this.$t("One record")}`, + first:this.$t("First"), + last:this.$t("Last"), + nextPage:'>', + nextChunk:'>>', + prevPage:'<', + prevChunk:'<<' + } + } } }, computed: { diff --git a/src/pages/Details.vue b/src/pages/Details.vue index c93e47d0..e4aeb28d 100644 --- a/src/pages/Details.vue +++ b/src/pages/Details.vue @@ -181,6 +181,7 @@ v-model="page" :records="importantHeartBeatList.length" :per-page="perPage" + :options="paginationConfig" /> @@ -237,6 +238,17 @@ export default { heartBeatList: [], toggleCertInfoBox: false, showPingChartBox: true, + paginationConfig: { + texts:{ + count:`${this.$t("Showing {from} to {to} of {count} records")}|{count} ${this.$t("records")}|${this.$t("One record")}`, + first:this.$t("First"), + last:this.$t("Last"), + nextPage:'>', + nextChunk:'>>', + prevPage:'<', + prevChunk:'<<' + } + } } }, computed: { From 15c4a8fb02d04776a733d4d05595d229e86574be Mon Sep 17 00:00:00 2001 From: Soroosh Date: Sun, 26 Sep 2021 18:52:38 +0330 Subject: [PATCH 05/23] Add RTL direction support to styles using postcss & rtlcss --- vite.config.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/vite.config.js b/vite.config.js index 58580547..6be31f5e 100644 --- a/vite.config.js +++ b/vite.config.js @@ -2,6 +2,9 @@ import legacy from "@vitejs/plugin-legacy" import vue from "@vitejs/plugin-vue" import { defineConfig } from "vite" +const postCssScss = require("postcss-scss") +const postcssRTLCSS = require('postcss-rtlcss'); + // https://vitejs.dev/config/ export default defineConfig({ plugins: [ @@ -10,5 +13,12 @@ export default defineConfig({ targets: ["ie > 11"], additionalLegacyPolyfills: ["regenerator-runtime/runtime"] }) - ] + ], + css: { + postcss: { + "parser": postCssScss, + "map": false, + "plugins": [postcssRTLCSS] + } + }, }) From 07c9d788295a65619c119f4028283c581dd92945 Mon Sep 17 00:00:00 2001 From: Soroosh Date: Sun, 26 Sep 2021 18:52:53 +0330 Subject: [PATCH 06/23] Update Farsi translations --- src/languages/fa.js | 203 +++++++++++++++++++++++--------------------- 1 file changed, 104 insertions(+), 99 deletions(-) diff --git a/src/languages/fa.js b/src/languages/fa.js index 436208e7..fe231fb3 100644 --- a/src/languages/fa.js +++ b/src/languages/fa.js @@ -4,28 +4,28 @@ export default { retryCheckEverySecond: "تکرار مجدد هر {0} ثانیه.", retriesDescription: "حداکثر تعداد تکرار پیش از علامت گذاری وب‌سایت بعنوان خارج از دسترس و ارسال اطلاع‌رسانی.", ignoreTLSError: "بی‌خیال ارور TLS/SSL برای سایت‌های HTTPS", - 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", + upsideDownModeDescription: "نتیجه وضعیت را برعکس کن، مثلا اگر سرویس در دسترس بود فرض کن که سرویس پایین است!", + maxRedirectDescription: "حداکثر تعداد ریدایرکتی که سرویس پشتیبانی کند. برای اینکه ری‌دایرکت‌ها پشتیبانی نشوند، عدد 0 را وارد کنید.", + acceptedStatusCodesDescription: "لطفا HTTP Status Code هایی که میخواهید به عنوان پاسخ موفقیت آمیز در نظر گرفته شود را انتخاب کنید.", + passwordNotMatchMsg: "تکرار رمز عبور مطابقت ندارد!", + notificationDescription: "برای اینکه سرویس اطلاع‌رسانی کار کند، آنرا به یکی از مانیتور‌ها متصل کنید.", + keywordDescription: "در نتیجه درخواست (اهمیتی ندارد پاسخ JSON است یا HTML) بدنبال این کلمه بگرد (حساس به کوچک/بزرگ بودن حروف).", pauseDashboardHome: "متوقف شده", - 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", - pauseMonitorMsg: "Are you sure want to pause?", - enableDefaultNotificationDescription: "For every new monitor this notification will be enabled by default. You can still disable the notification separately for each monitor.", - clearEventsMsg: "Are you sure want to delete all events for this monitor?", - clearHeartbeatsMsg: "Are you sure want to delete all heartbeats for this monitor?", - confirmClearStatisticsMsg: "Are you sure want to delete ALL statistics?", - importHandleDescription: "Choose 'Skip existing' if you want to skip every monitor or notification with the same name. 'Overwrite' will delete every existing monitor and notification.", - confirmImportMsg: "Are you sure to import the backup? Please make sure you've selected the right import option.", - twoFAVerifyLabel: "Please type in your token to verify that 2FA is working", - tokenValidSettingsMsg: "Token is valid! You can now save the 2FA settings.", - confirmEnableTwoFAMsg: "Are you sure you want to enable 2FA?", - confirmDisableTwoFAMsg: "Are you sure you want to disable 2FA?", + deleteMonitorMsg: "آیا از حذف این مانیتور مطمئن هستید؟", + deleteNotificationMsg: "آیا مطمئن هستید که میخواهید این سرویس اطلاع‌رسانی را برای تمامی مانیتورها حذف کنید؟", + resoverserverDescription: "سرویس CloudFlare به عنوان سرور پیش‌فرض استفاده می‌شود، شما میتوانید آنرا به هر سرور دیگری بعدا تغییر دهید.", + rrtypeDescription: "لطفا نوع Resource Record را انتخاب کنید.", + pauseMonitorMsg: "آیا مطمئن هستید که میخواهید این مانیتور را متوقف کنید ؟", + enableDefaultNotificationDescription: "برای هر مانیتور جدید، این سرویس اطلاع‌رسانی به صورت پیش‌فرض فعال خواهد شد. البته که شما میتوانید به صورت دستی آنرا برای هر مانیتور به صورت جداگانه غیر فعال کنید.", + clearEventsMsg: "آیا از اینکه تمامی تاریخچه رویداد‌های این مانیتور حذف شود مطمئن هستید؟", + clearHeartbeatsMsg: "آیا از اینکه تاریخچه تمامی Heartbeat های این مانیتور حذف شود مطمئن هستید؟ ", + confirmClearStatisticsMsg: "آیا از حذف تمامی آمار و ارقام مطمئن هستید؟", + importHandleDescription: " اگر که میخواهید بیخیال مانیتورها و یا سرویس‌های اطلاع‌رسانی که با نام مشابه از قبل موجود هستند شوید، گزینه 'بی‌خیال موارد ..' را انتخاب کنید. توجه کنید که گزینه 'بازنویسی' تمامی موارد موجود با نام مشابه را از بین خواهد برد.", + confirmImportMsg: "آیا از بازگردانی بک آپ مطمئن هستید؟ لطفا از اینکه نوع بازگردانی درستی را انتخاب کرده‌اید اطمینان حاصل کنید!", + twoFAVerifyLabel: "لطفا جهت اطمینان از عملکرد احراز هویت دو مرحله‌ای توکن خود را وارد کنید!", + tokenValidSettingsMsg: "توکن شما معتبر است، هم اکنون میتوانید احراز هویت دو مرحله‌ای را فعال کنید!", + confirmEnableTwoFAMsg: " آیا از فعال سازی احراز هویت دو مرحله‌ای مطمئن هستید؟", + confirmDisableTwoFAMsg: "آیا از غیرفعال سازی احراز هویت دومرحله‌ای مطمئن هستید؟", Settings: "تنظیمات", Dashboard: "پیشخوان", "New Update": "بروزرسانی جدید!", @@ -34,7 +34,7 @@ export default { Theme: "پوسته", General: "عمومی", Version: "نسخه", - "Check Update On GitHub": "بررسی بروز رسانی بر روی گیت‌هاب", + "Check Update On GitHub": "بررسی بروزرسانی بر روی گیت‌هاب", List: "لیست", Add: "اضافه", "Add New Monitor": "اضافه کردن مانیتور جدید", @@ -48,7 +48,7 @@ export default { Status: "وضعیت", DateTime: "تاریخ و زمان", Message: "پیام", - "No important events": "رخداد جدید نیست.", + "No important events": "رخداد جدیدی موجود نیست.", Resume: "ادامه", Edit: "ویرایش", Delete: "حذف", @@ -65,7 +65,7 @@ export default { "Monitor Type": "نوع مانیتور", Keyword: "کلمه کلیدی", "Friendly Name": "عنوان", - URL: "URL", + URL: "آدرس (URL)", Hostname: "نام میزبان (Hostname)", Port: "پورت", "Heartbeat Interval": "فاصله هر Heartbeat", @@ -82,23 +82,23 @@ export default { Light: "روشن", Dark: "تاریک", Auto: "اتوماتیک", - "Theme - Heartbeat Bar": "Theme - Heartbeat Bar", - Normal: "Normal", - Bottom: "Bottom", - None: "None", + "Theme - Heartbeat Bar": "ظاهر نوار Heartbeat", + Normal: "معمولی", + Bottom: "پایین", + None: "هیچ کدام", Timezone: "موقعیت زمانی", - "Search Engine Visibility": "Search Engine Visibility", - "Allow indexing": "Allow indexing", - "Discourage search engines from indexing site": "Discourage search engines from indexing site", - "Change Password": "Change Password", - "Current Password": "Current Password", - "New Password": "New Password", - "Repeat New Password": "Repeat New Password", - "Update Password": "Update Password", - "Disable Auth": "Disable Auth", - "Enable Auth": "Enable Auth", + "Search Engine Visibility": "قابلیت دسترسی برای موتورهای جستجو", + "Allow indexing": "اجازه ایندکس شدن را بده.", + "Discourage search engines from indexing site": "به موتورهای جستجو اجازه ایندکس کردن این سامانه را نده.", + "Change Password": "تغییر رمزعبور", + "Current Password": "رمزعبور فعلی", + "New Password": "رمزعبور جدید", + "Repeat New Password": "تکرار رمزعبور جدید", + "Update Password": "بروز رسانی رمز عبور", + "Disable Auth": "غیر فعال سازی تایید هویت", + "Enable Auth": "فعال سازی تایید هویت", Logout: "خروج", - Leave: "Leave", + Leave: "منصرف شدم", "I understand, please disable": "متوجه هست»، لطفا غیرفعال کنید!", Confirm: "تایید", Yes: "بلی", @@ -107,66 +107,71 @@ export default { Password: "کلمه عبور", "Remember me": "مراب هب خاطر بسپار", Login: "ورود", - "No Monitors, please": "No Monitors, please", - "add one": "add one", - "Notification Type": "Notification Type", - Email: "Email", - Test: "Test", - "Certificate Info": "Certificate Info", - "Resolver Server": "Resolver Server", - "Resource Record Type": "Resource Record Type", - "Last Result": "Last Result", - "Create your admin account": "Create your admin account", - "Repeat Password": "Repeat Password", - "Import Backup": "Import Backup", - "Export Backup": "Export Backup", - Export: "Export", - Import: "Import", - respTime: "Resp. Time (ms)", - notAvailableShort: "N/A", - "Default enabled": "Default enabled", - "Apply on all existing monitors": "Apply on all existing monitors", - Create: "Create", - "Clear Data": "Clear Data", - Events: "Events", + "No Monitors, please": "هیچ مانیتوری موجود نیست، لطفا", + "add one": "یک مورد اضافه کنید", + "Notification Type": "نوع اطلاع‌رسانی", + Email: "ایمیل", + Test: "تست", + "Certificate Info": "اطلاعات سرتیفیکت", + "Resolver Server": "سرور Resolver", + "Resource Record Type": "نوع رکورد (Resource Record Type)", + "Last Result": "آخرین نتیجه", + "Create your admin account": "ایجاد حساب کاربری مدیر", + "Repeat Password": "تکرار رمز عبور", + "Import Backup": "بازگردانی فایل پشتیبان", + "Export Backup": "ذخیره فایل پشتیبان", + Export: "استخراج اطلاعات", + Import: "ورود اطلاعات", + respTime: "زمان پاسخگویی (میلی‌ثانیه)", + notAvailableShort: "ناموجود", + "Default enabled": "به صورت پیش‌فرض فعال باشد.", + "Apply on all existing monitors": "بر روی تمامی مانیتور‌های فعلی اعمال شود.", + Create: "ایجاد", + "Clear Data": "پاکسازی داده‌ها", + 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.", - alertNoFile: "Please select a file to import.", - alertWrongFileType: "Please select a JSON file.", - "Clear all statistics": "Clear all Statistics", - "Skip existing": "Skip existing", - Overwrite: "Overwrite", - Options: "Options", - "Keep both": "Keep both", - "Verify Token": "Verify Token", - "Setup 2FA": "Setup 2FA", - "Enable 2FA": "Enable 2FA", - "Disable 2FA": "Disable 2FA", - "2FA Settings": "2FA Settings", - "Two Factor Authentication": "Two Factor Authentication", - Active: "Active", - Inactive: "Inactive", - Token: "Token", - "Show URI": "Show URI", - Tags: "Tags", - "Add New below or Select...": "Add New below or Select...", - "Tag with this name already exist.": "Tag with this name already exist.", - "Tag with this value already exist.": "Tag with this value already exist.", - color: "color", - "value (optional)": "value (optional)", - Gray: "Gray", - Red: "Red", - Orange: "Orange", - Green: "Green", - Blue: "Blue", - Indigo: "Indigo", - Purple: "Purple", - Pink: "Pink", - "Search...": "Search...", - "Avg. Ping": "Avg. Ping", - "Avg. Response": "Avg. Response", - "Uptime Kuma": "آپتایم کوما" + backupDescription: "شما میتوانید تمامی مانیتورها و تنظیمات اطلاع‌رسانی‌ها را در قالب یه فایل JSON دریافت کنید.", + backupDescription2: "البته تاریخچه رخدادها دراین فایل قرار نخواهند داشت.", + backupDescription3: "توجه داشته باشید که تمامی اطلاعات حساس شما مانند توکن‌ها نیز در این فایل وجود خواهد داشت ، پس از این فایل به خوبی مراقبت کنید.", + alertNoFile: "لطفا یک فایل برای «ورود اطلاعات» انتخاب کنید..", + alertWrongFileType: "یک فایل JSON انتخاب کنید.", + "Clear all statistics": "پاکسازی تمامی آمار و ارقام", + "Skip existing": "بی‌خیال مواردی که از قبل موجود است", + Overwrite: "بازنویسی", + Options: "تنظیمات", + "Keep both": "هر دو را نگه‌ دار", + "Verify Token": "تایید توکن", + "Setup 2FA": "تنظیمات احراز دو مرحله‌ای", + "Enable 2FA": "فعال سازی احراز 2 مرحله‌ای", + "Disable 2FA": "غیر فعال کردن احراز 2 مرحله‌ای", + "2FA Settings": "تنظیمات احراز 2 مرحله‌ای", + "Two Factor Authentication": "احراز هویت دومرحله‌ای", + Active: "فعال", + Inactive: "غیرفعال", + Token: "توکن", + "Show URI": "نمایش آدرس (URI) ", + Tags: "برچسب‌ها", + "Add New below or Select...": "یک مورد جدید اضافه کنید و یا از لیست انتخاب کنید...", + "Tag with this name already exist.": "یک برچسب با این «نام» از قبل وجود دارد", + "Tag with this value already exist.": "یک برچسب با این «مقدار» از قبل وجود دارد.", + color: "رنگ", + "value (optional)": "مقدار (اختیاری)", + Gray: "خاکستری", + Red: "قرمز", + Orange: "نارنجی", + Green: "سبز", + Blue: "آبی", + Indigo: "نیلی", + Purple: "بنفش", + Pink: "صورتی", + "Search...": "جستجو...", + "Avg. Ping": "متوسط پینگ", + "Avg. Response": "متوسط زمان پاسخ", + "Uptime Kuma": "آپتایم کوما", + "records":"مورد", + "One record":"یک مورد", + "Showing {from} to {to} of {count} records":"نمایش از {from} تا {to} از {count} مورد", + 'First': 'اولین', + 'Last': 'آخرین' } From 56d8f585fd61be9b09d9b221a086eaf532b49278 Mon Sep 17 00:00:00 2001 From: Soroosh Date: Sun, 26 Sep 2021 18:54:51 +0330 Subject: [PATCH 07/23] Add postcss-rtlcss and postcss-scss --- package.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 0f7a7157..84865e61 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,8 @@ "nodemailer": "^6.6.3", "notp": "^2.0.3", "password-hash": "^1.2.2", + "postcss-rtlcss": "^3.4.1", + "postcss-scss": "^4.0.0", "prom-client": "^13.2.0", "prometheus-api-metrics": "^3.2.0", "qrcode": "^1.4.4", @@ -70,8 +72,8 @@ "socket.io-client": "^4.2.0", "sqlite3": "github:mapbox/node-sqlite3#593c9d", "tcp-ping": "^0.1.1", - "timezones-list": "^3.0.1", "thirty-two": "^1.0.2", + "timezones-list": "^3.0.1", "v-pagination-3": "^0.1.6", "vue": "^3.2.8", "vue-chart-3": "^0.5.8", From 47749ca58d86f1c958fd3e65da1c0d333407b629 Mon Sep 17 00:00:00 2001 From: Soroosh Date: Sun, 26 Sep 2021 19:56:43 +0330 Subject: [PATCH 08/23] Replace some hardcoded with translations --- src/pages/StatusPage.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/StatusPage.vue b/src/pages/StatusPage.vue index 42a15af0..cbc30144 100644 --- a/src/pages/StatusPage.vue +++ b/src/pages/StatusPage.vue @@ -197,7 +197,7 @@ From 1448de7b19dc61a1cfa58e5bfdf31ff824e4385f Mon Sep 17 00:00:00 2001 From: Soroosh Date: Mon, 27 Sep 2021 00:32:51 +0330 Subject: [PATCH 09/23] Update some translations --- src/languages/fa.js | 22 +++++++++++----------- src/pages/Settings.vue | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/languages/fa.js b/src/languages/fa.js index 77f141df..15478666 100644 --- a/src/languages/fa.js +++ b/src/languages/fa.js @@ -99,7 +99,7 @@ export default { "Enable Auth": "فعال سازی تایید هویت", Logout: "خروج", Leave: "منصرف شدم", - "I understand, please disable": "متوجه هست»، لطفا غیرفعال کنید!", + "I understand, please disable": "متوجه هستم، لطفا غیرفعال کنید!", Confirm: "تایید", Yes: "بلی", No: "خیر", @@ -169,7 +169,7 @@ export default { "Avg. Ping": "متوسط پینگ", "Avg. Response": "متوسط زمان پاسخ", "Entry Page": "صفحه ورودی", - "statusPageNothing": "چیزی اینجا نیست، لطفا یک گروه و یا یک مانیتور اضافه کنید!", + statusPageNothing: "چیزی اینجا نیست، لطفا یک گروه و یا یک مانیتور اضافه کنید!", "No Services": "هیچ سرویسی موجود نیست", "All Systems Operational": "تمامی سیستم‌ها عملیاتی هستند!", "Partially Degraded Service": "افت نسبی کیفیت سرویس", @@ -177,14 +177,14 @@ export default { "Add Group": "اضافه کردن گروه", "Add a monitor": "اضافه کردن مانیتور", "Edit Status Page": "ویرایش صفحه وضعیت", - "Status Page":"صفحه وضعیت", + "Status Page": "صفحه وضعیت", "Go to Dashboard": "رفتن به پیشخوان", "Uptime Kuma": "آپتایم کوما", - "records":"مورد", - "One record":"یک مورد", - "Showing {from} to {to} of {count} records":"نمایش از {from} تا {to} از {count} مورد", - 'First': 'اولین', - 'Last': 'آخرین', - 'Info':'اطلاعات', - 'Powered By':'نیرو گرفته از' -} + records: "مورد", + "One record": "یک مورد", + "Showing {from} to {to} of {count} records": "نمایش از {from} تا {to} از {count} مورد", + First: "اولین", + Last: "آخرین", + Info: "اطلاعات", + "Powered By": "نیرو گرفته از", +}; diff --git a/src/pages/Settings.vue b/src/pages/Settings.vue index 7c808f6b..3f7ce23c 100644 --- a/src/pages/Settings.vue +++ b/src/pages/Settings.vue @@ -226,7 +226,7 @@ {{ $t("Setup Notification") }} -

Info

+

{{ $t("Info") }}

{{ $t("Version") }}: {{ $root.info.version }}
{{ $t("Check Update On GitHub") }} From 9f06d54688842fd546c26722b1087d282d7882ad Mon Sep 17 00:00:00 2001 From: Soroosh Date: Mon, 27 Sep 2021 09:37:43 +0330 Subject: [PATCH 10/23] Remove font import and update font-family for lang fa --- src/assets/localization.scss | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/assets/localization.scss b/src/assets/localization.scss index c7eb9040..f9a28d8a 100644 --- a/src/assets/localization.scss +++ b/src/assets/localization.scss @@ -1,7 +1,5 @@ -@import url('http://fonts.cdnfonts.com/css/iranian-sans'); - html[lang='fa'] { #app { - font-family: 'Iranian Sans', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, segoe ui, Roboto, helvetica neue, Arial, noto sans, sans-serif, apple color emoji, segoe ui emoji, segoe ui symbol, noto color emoji; + font-family: 'IRANSans', 'Iranian Sans','B Nazanin', 'Tahoma', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, segoe ui, Roboto, helvetica neue, Arial, noto sans, sans-serif, apple color emoji, segoe ui emoji, segoe ui symbol, noto color emoji; } } \ No newline at end of file From 1ed4ac94945440607ff06ebee54bd5306da0077d Mon Sep 17 00:00:00 2001 From: LouisLam Date: Fri, 1 Oct 2021 00:09:43 +0800 Subject: [PATCH 11/23] add Push-based monitoring (#279) --- db/patch-monitor-push_token.sql | 7 ++ server/database.js | 1 + server/model/monitor.js | 36 ++++++++- server/routers/api-router.js | 38 ++++++++++ server/server.js | 7 +- server/util-server.js | 10 --- src/assets/app.scss | 5 ++ src/components/CopyableInput.vue | 122 +++++++++++++++++++++++++++++++ src/icon.js | 7 +- src/mixins/public.js | 8 ++ src/pages/EditMonitor.vue | 59 ++++++++++++--- src/util.js | 12 ++- src/util.ts | 10 +++ 13 files changed, 292 insertions(+), 30 deletions(-) create mode 100644 db/patch-monitor-push_token.sql create mode 100644 src/components/CopyableInput.vue diff --git a/db/patch-monitor-push_token.sql b/db/patch-monitor-push_token.sql new file mode 100644 index 00000000..8c2e7a42 --- /dev/null +++ b/db/patch-monitor-push_token.sql @@ -0,0 +1,7 @@ +-- You should not modify if this have pushed to Github, unless it does serious wrong with the db. +BEGIN TRANSACTION; + +ALTER TABLE monitor + ADD push_token VARCHAR(20) DEFAULT NULL; + +COMMIT; diff --git a/server/database.js b/server/database.js index 4cf1e393..47eca283 100644 --- a/server/database.js +++ b/server/database.js @@ -48,6 +48,7 @@ class Database { "patch-add-retry-interval-monitor.sql": true, "patch-incident-table.sql": true, "patch-group-table.sql": true, + "patch-monitor-push_token.sql": true, } /** diff --git a/server/model/monitor.js b/server/model/monitor.js index a50baccf..c551fa7d 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -69,6 +69,7 @@ class Monitor extends BeanModel { dns_resolve_type: this.dns_resolve_type, dns_resolve_server: this.dns_resolve_server, dns_last_result: this.dns_last_result, + pushToken: this.pushToken, notificationIDList, tags: tags, }; @@ -236,6 +237,28 @@ class Monitor extends BeanModel { bean.msg = dnsMessage; bean.status = UP; + } else if (this.type === "push") { // Type: Push + const time = R.isoDateTime(dayjs.utc().subtract(this.interval, "second")); + + let heartbeatCount = await R.count("heartbeat", " monitor_id = ? AND time > ? ", [ + this.id, + time + ]); + + debug("heartbeatCount" + heartbeatCount + " " + time); + + if (heartbeatCount <= 0) { + throw new Error("No heartbeat in the time window"); + } else { + // No need to insert successful heartbeat for push type, so end here + retries = 0; + this.heartbeatInterval = setTimeout(beat, this.interval * 1000); + return; + } + + } else { + bean.msg = "Unknown Monitor Type"; + bean.status = PENDING; } if (this.isUpsideDown()) { @@ -263,6 +286,8 @@ class Monitor extends BeanModel { } } + let beatInterval = this.interval; + // * ? -> ANY STATUS = important [isFirstBeat] // UP -> PENDING = not important // * UP -> DOWN = important @@ -312,8 +337,6 @@ class Monitor extends BeanModel { bean.important = false; } - let beatInterval = this.interval; - if (bean.status === UP) { console.info(`Monitor #${this.id} '${this.name}': Successful Response: ${bean.ping} ms | Interval: ${beatInterval} seconds | Type: ${this.type}`); } else if (bean.status === PENDING) { @@ -339,7 +362,14 @@ class Monitor extends BeanModel { }; - beat(); + // Delay Push Type + if (this.type === "push") { + setTimeout(() => { + beat(); + }, this.interval * 1000); + } else { + beat(); + } } stop() { diff --git a/server/routers/api-router.js b/server/routers/api-router.js index b56efcb2..6c87fb17 100644 --- a/server/routers/api-router.js +++ b/server/routers/api-router.js @@ -4,15 +4,53 @@ const { R } = require("redbean-node"); const server = require("../server"); const apicache = require("../modules/apicache"); const Monitor = require("../model/monitor"); +const dayjs = require("dayjs"); +const { UP } = require("../../src/util"); let router = express.Router(); let cache = apicache.middleware; +let io = server.io; router.get("/api/entry-page", async (_, response) => { allowDevAllOrigin(response); response.json(server.entryPage); }); +router.get("/api/push/:pushToken", async (request, response) => { + try { + let pushToken = request.params.pushToken; + let msg = request.query.msg || "OK"; + + let monitor = await R.findOne("monitor", " push_token = ? AND active = 1 ", [ + pushToken + ]); + + if (! monitor) { + throw new Error("Monitor not found or not active."); + } + + let bean = R.dispense("heartbeat"); + bean.monitor_id = monitor.id; + bean.time = R.isoDateTime(dayjs.utc()); + bean.status = UP; + bean.msg = msg; + + await R.store(bean); + + io.to(monitor.user_id).emit("heartbeat", bean.toJSON()); + Monitor.sendStats(io, monitor.id, monitor.user_id); + + response.json({ + ok: true, + }); + } catch (e) { + response.json({ + ok: false, + msg: e.message + }); + } +}); + // Status Page Config router.get("/api/status-page/config", async (_request, response) => { allowDevAllOrigin(response); diff --git a/server/server.js b/server/server.js index ddd68695..2384acf5 100644 --- a/server/server.js +++ b/server/server.js @@ -6,7 +6,7 @@ if (! process.env.NODE_ENV) { console.log("Node Env: " + process.env.NODE_ENV); -const { sleep, debug, TimeLogger, getRandomInt } = require("../src/util"); +const { sleep, debug, getRandomInt, genSecret } = require("../src/util"); console.log("Importing Node libraries"); const fs = require("fs"); @@ -37,7 +37,7 @@ console.log("Importing this project modules"); debug("Importing Monitor"); const Monitor = require("./model/monitor"); debug("Importing Settings"); -const { getSettings, setSettings, setting, initJWTSecret, genSecret, allowDevAllOrigin, checkLogin } = require("./util-server"); +const { getSettings, setSettings, setting, initJWTSecret, checkLogin } = require("./util-server"); debug("Importing Notification"); const { Notification } = require("./notification"); @@ -71,7 +71,7 @@ if (demoMode) { console.log("==== Demo Mode ===="); } -console.log("Creating express and socket.io instance") +console.log("Creating express and socket.io instance"); const app = express(); let server; @@ -511,6 +511,7 @@ exports.entryPage = "dashboard"; bean.accepted_statuscodes_json = JSON.stringify(monitor.accepted_statuscodes); bean.dns_resolve_type = monitor.dns_resolve_type; bean.dns_resolve_server = monitor.dns_resolve_server; + bean.pushToken = monitor.pushToken; await R.store(bean); diff --git a/server/util-server.js b/server/util-server.js index 4d2b6cbe..29e4b11f 100644 --- a/server/util-server.js +++ b/server/util-server.js @@ -272,16 +272,6 @@ exports.getTotalClientInRoom = (io, roomName) => { } }; -exports.genSecret = () => { - let secret = ""; - let chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - let charsLength = chars.length; - for ( let i = 0; i < 64; i++ ) { - secret += chars.charAt(Math.floor(Math.random() * charsLength)); - } - return secret; -}; - exports.allowDevAllOrigin = (res) => { if (process.env.NODE_ENV === "development") { exports.allowAllOrigin(res); diff --git a/src/assets/app.scss b/src/assets/app.scss index f4707df9..bc294932 100644 --- a/src/assets/app.scss +++ b/src/assets/app.scss @@ -180,6 +180,11 @@ h2 { border-color: $dark-border-color; } + .form-control:disabled, .form-control[readonly] { + background-color: #232f3b; + opacity: 1; + } + .table-hover > tbody > tr:hover { --bs-table-accent-bg: #070a10; color: $dark-font-color; diff --git a/src/components/CopyableInput.vue b/src/components/CopyableInput.vue new file mode 100644 index 00000000..d777f4a0 --- /dev/null +++ b/src/components/CopyableInput.vue @@ -0,0 +1,122 @@ + + + diff --git a/src/icon.js b/src/icon.js index 67eb2a76..6fb91498 100644 --- a/src/icon.js +++ b/src/icon.js @@ -26,7 +26,10 @@ import { faArrowsAltV, faUnlink, faQuestionCircle, - faImages, faUpload, + faImages, + faUpload, + faCopy, + faCheck, } from "@fortawesome/free-solid-svg-icons"; library.add( @@ -54,6 +57,8 @@ library.add( faQuestionCircle, faImages, faUpload, + faCopy, + faCheck, ); export { FontAwesomeIcon }; diff --git a/src/mixins/public.js b/src/mixins/public.js index 2aa180cd..ba8457a0 100644 --- a/src/mixins/public.js +++ b/src/mixins/public.js @@ -36,5 +36,13 @@ export default { return result; }, + + baseURL() { + if (env === "development" || localStorage.dev === "dev") { + return axios.defaults.baseURL; + } else { + return location.protocol + "//" + location.host; + } + } } }; diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue index a5614701..7e136a59 100644 --- a/src/pages/EditMonitor.vue +++ b/src/pages/EditMonitor.vue @@ -26,19 +26,31 @@ + +
+
+ +
+ + +
+ +
@@ -210,13 +222,17 @@