mirror of
				https://github.com/louislam/uptime-kuma.git
				synced 2025-11-04 13:46:13 +08:00 
			
		
		
		
	Merge branch 'master' into clean-mobile-table
This commit is contained in:
		@@ -16,6 +16,7 @@ module.exports = {
 | 
			
		||||
        requireConfigFile: false,
 | 
			
		||||
    },
 | 
			
		||||
    rules: {
 | 
			
		||||
        "camelcase": "warn",
 | 
			
		||||
        // override/add rules settings here, such as:
 | 
			
		||||
        // 'vue/no-unused-vars': 'error'
 | 
			
		||||
        "no-unused-vars": "warn",
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,38 @@ The project was created with vite.js (vue3). Then I created a sub-directory call
 | 
			
		||||
 | 
			
		||||
The frontend code build into "dist" directory. The server uses "dist" as root. This is how production is working.
 | 
			
		||||
 | 
			
		||||
Your IDE should follow the config in ".editorconfig". The most special thing is I set it to 4 spaces indentation. I know 2 spaces indentation became a kind of standard nowadays for js, but my eyes is not so comfortable for this. In my opinion, there is no callback-hell nowadays, it is good to go back 4 spaces world again.
 | 
			
		||||
# Can I create a pull request for Uptime Kuma?
 | 
			
		||||
 | 
			
		||||
Generally, if the pull request is working fine and it do not affect any existing logic, workflow and perfomance, I will merge to the master branch once it is tested.
 | 
			
		||||
 | 
			
		||||
If you are not sure, feel free to create an empty pull request draft first.
 | 
			
		||||
 | 
			
		||||
## Pull Request Examples
 | 
			
		||||
 | 
			
		||||
### ✅ High - Medium Priority
 | 
			
		||||
 | 
			
		||||
- Add a new notification
 | 
			
		||||
- Add a chart
 | 
			
		||||
- Fix a bug
 | 
			
		||||
 | 
			
		||||
### *️⃣ Requires one more reviewer 
 | 
			
		||||
 | 
			
		||||
I do not have such knowledge to test it
 | 
			
		||||
 | 
			
		||||
- Add k8s supports 
 | 
			
		||||
 | 
			
		||||
### *️⃣ Low Priority 
 | 
			
		||||
 | 
			
		||||
It chnaged my current workflow and require further studies.
 | 
			
		||||
 | 
			
		||||
- Change my release approach
 | 
			
		||||
 | 
			
		||||
### ❌ Won't Merge
 | 
			
		||||
 | 
			
		||||
- Duplicated pull request
 | 
			
		||||
- Buggy
 | 
			
		||||
- Existing logic is completely modified or deleted
 | 
			
		||||
- A function that is completely out of scope
 | 
			
		||||
 | 
			
		||||
# Project Styles
 | 
			
		||||
 | 
			
		||||
@@ -19,16 +50,27 @@ For example, recently, because I am not a python expert, I spent a 2 hours to re
 | 
			
		||||
- All settings in frontend.
 | 
			
		||||
- Easy to use
 | 
			
		||||
 | 
			
		||||
# Coding Styles
 | 
			
		||||
 | 
			
		||||
- Follow .editorconfig
 | 
			
		||||
- Follow eslint
 | 
			
		||||
 | 
			
		||||
## Name convention
 | 
			
		||||
 | 
			
		||||
- Javascript/Typescript: camelCaseType
 | 
			
		||||
- SQLite: underscore_type
 | 
			
		||||
- CSS/SCSS: dash-type
 | 
			
		||||
 | 
			
		||||
# Tools
 | 
			
		||||
- Node.js >= 14
 | 
			
		||||
- Git
 | 
			
		||||
- IDE that supports .editorconfig  (I am using Intellji Idea)
 | 
			
		||||
- IDE that supports .editorconfig and eslint (I am using Intellji Idea)
 | 
			
		||||
- A SQLite tool (I am using SQLite Expert Personal)
 | 
			
		||||
 | 
			
		||||
# Prepare the dev
 | 
			
		||||
# Install dependencies 
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
npm install
 | 
			
		||||
npm install --dev
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
# Backend Dev
 | 
			
		||||
@@ -39,7 +81,6 @@ npm run start-server
 | 
			
		||||
# Or 
 | 
			
		||||
 | 
			
		||||
node server/server.js
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
It binds to 0.0.0.0:3001 by default.
 | 
			
		||||
@@ -92,7 +133,8 @@ The data and socket logic in "src/mixins/socket.js"
 | 
			
		||||
 | 
			
		||||
# Database Migration
 | 
			
		||||
 | 
			
		||||
TODO
 | 
			
		||||
1. create `patch{num}.sql` in `./db/`
 | 
			
		||||
1. update `latestVersion` in `./server/database.js`
 | 
			
		||||
 | 
			
		||||
# Unit Test
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										8140
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										8140
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -10,6 +10,9 @@
 | 
			
		||||
        "node": "14.*"
 | 
			
		||||
    },
 | 
			
		||||
    "scripts": {
 | 
			
		||||
        "lint:js": "eslint --ext \".js,.vue\" --ignore-path .gitignore .",
 | 
			
		||||
        "lint:style": "stylelint \"**/*.{vue,css}\" --ignore-path .gitignore",
 | 
			
		||||
        "lint": "npm run lint:js && npm run lint:style",
 | 
			
		||||
        "dev": "vite --host",
 | 
			
		||||
        "start": "npm run start-server",
 | 
			
		||||
        "start-server": "node server/server.js",
 | 
			
		||||
@@ -64,6 +67,7 @@
 | 
			
		||||
        "vue": "^3.2.2",
 | 
			
		||||
        "vue-chart-3": "^0.5.7",
 | 
			
		||||
        "vue-confirm-dialog": "^1.0.2",
 | 
			
		||||
        "vue-i18n": "^9.1.7",
 | 
			
		||||
        "vue-multiselect": "^3.0.0-alpha.2",
 | 
			
		||||
        "vue-router": "^4.0.11",
 | 
			
		||||
        "vue-toastification": "^2.0.0-rc.1"
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,6 @@
 | 
			
		||||
const fs = require("fs");
 | 
			
		||||
const { sleep, debug, isDev } = require("../src/util");
 | 
			
		||||
const { R } = require("redbean-node");
 | 
			
		||||
const { setSetting, setting } = require("./util-server");
 | 
			
		||||
const knex = require("knex");
 | 
			
		||||
 | 
			
		||||
class Database {
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@
 | 
			
		||||
            <div class="modal-content">
 | 
			
		||||
                <div class="modal-header">
 | 
			
		||||
                    <h5 id="exampleModalLabel" class="modal-title">
 | 
			
		||||
                        Confirm
 | 
			
		||||
                        {{ $t("Confirm") }}
 | 
			
		||||
                    </h5>
 | 
			
		||||
                    <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" />
 | 
			
		||||
                </div>
 | 
			
		||||
@@ -35,7 +35,7 @@ export default {
 | 
			
		||||
        },
 | 
			
		||||
        yesText: {
 | 
			
		||||
            type: String,
 | 
			
		||||
            default: "Yes",
 | 
			
		||||
            default: "Yes",     // TODO: No idea what to translate this
 | 
			
		||||
        },
 | 
			
		||||
        noText: {
 | 
			
		||||
            type: String,
 | 
			
		||||
 
 | 
			
		||||
@@ -6,12 +6,12 @@
 | 
			
		||||
 | 
			
		||||
                <div class="form-floating">
 | 
			
		||||
                    <input id="floatingInput" v-model="username" type="text" class="form-control" placeholder="Username">
 | 
			
		||||
                    <label for="floatingInput">Username</label>
 | 
			
		||||
                    <label for="floatingInput">{{ $t("Username") }}</label>
 | 
			
		||||
                </div>
 | 
			
		||||
 | 
			
		||||
                <div class="form-floating mt-3">
 | 
			
		||||
                    <input id="floatingPassword" v-model="password" type="password" class="form-control" placeholder="Password">
 | 
			
		||||
                    <label for="floatingPassword">Password</label>
 | 
			
		||||
                    <label for="floatingPassword">{{ $t("Password") }}</label>
 | 
			
		||||
                </div>
 | 
			
		||||
 | 
			
		||||
                <div class="form-check mb-3 mt-3 d-flex justify-content-center pe-4">
 | 
			
		||||
@@ -19,12 +19,12 @@
 | 
			
		||||
                        <input id="remember" v-model="$root.remember" type="checkbox" value="remember-me" class="form-check-input">
 | 
			
		||||
 | 
			
		||||
                        <label class="form-check-label" for="remember">
 | 
			
		||||
                            Remember me
 | 
			
		||||
                            {{ $t("Remember me") }}
 | 
			
		||||
                        </label>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <button class="w-100 btn btn-primary" type="submit" :disabled="processing">
 | 
			
		||||
                    Login
 | 
			
		||||
                    {{ $t("Login") }}
 | 
			
		||||
                </button>
 | 
			
		||||
 | 
			
		||||
                <div v-if="res && !res.ok" class="alert alert-danger mt-3" role="alert">
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div class="shadow-box list mb-4">
 | 
			
		||||
        <div v-if="Object.keys($root.monitorList).length === 0" class="text-center mt-3">
 | 
			
		||||
            No Monitors, please <router-link to="/add">add one</router-link>.
 | 
			
		||||
            {{ $t("No Monitors, please") }} <router-link to="/add">{{ $t("add one") }}</router-link>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <router-link v-for="(item, index) in sortedMonitorList" :key="index" :to="monitorURL(item.id)" class="item" :class="{ 'disabled': ! item.active }">
 | 
			
		||||
 
 | 
			
		||||
@@ -5,17 +5,17 @@
 | 
			
		||||
                <div class="modal-content">
 | 
			
		||||
                    <div class="modal-header">
 | 
			
		||||
                        <h5 id="exampleModalLabel" class="modal-title">
 | 
			
		||||
                            Setup Notification
 | 
			
		||||
                            {{ $t("Setup Notification") }}
 | 
			
		||||
                        </h5>
 | 
			
		||||
                        <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" />
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="modal-body">
 | 
			
		||||
                        <div class="mb-3">
 | 
			
		||||
                            <label for="type" class="form-label">Notification Type</label>
 | 
			
		||||
                            <label for="type" class="form-label">{{ $t("Notification Type") }}</label>
 | 
			
		||||
                            <select id="type" v-model="notification.type" class="form-select">
 | 
			
		||||
                                <option value="telegram">Telegram</option>
 | 
			
		||||
                                <option value="webhook">Webhook</option>
 | 
			
		||||
                                <option value="smtp">Email (SMTP)</option>
 | 
			
		||||
                                <option value="smtp">{{ $t("Email") }} (SMTP)</option>
 | 
			
		||||
                                <option value="discord">Discord</option>
 | 
			
		||||
                                <option value="signal">Signal</option>
 | 
			
		||||
                                <option value="gotify">Gotify</option>
 | 
			
		||||
@@ -31,7 +31,7 @@
 | 
			
		||||
                        </div>
 | 
			
		||||
 | 
			
		||||
                        <div class="mb-3">
 | 
			
		||||
                            <label for="name" class="form-label">Friendly Name</label>
 | 
			
		||||
                            <label for="name" class="form-label">{{ $t("Friendly Name") }}</label>
 | 
			
		||||
                            <input id="name" v-model="notification.name" type="text" class="form-control" required>
 | 
			
		||||
                        </div>
 | 
			
		||||
 | 
			
		||||
@@ -407,13 +407,13 @@
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="modal-footer">
 | 
			
		||||
                        <button v-if="id" type="button" class="btn btn-danger" :disabled="processing" @click="deleteConfirm">
 | 
			
		||||
                            Delete
 | 
			
		||||
                            {{ $t("Delete") }}
 | 
			
		||||
                        </button>
 | 
			
		||||
                        <button type="button" class="btn btn-warning" :disabled="processing" @click="test">
 | 
			
		||||
                            Test
 | 
			
		||||
                            {{ $t("Test") }}
 | 
			
		||||
                        </button>
 | 
			
		||||
                        <button type="submit" class="btn btn-primary" :disabled="processing">
 | 
			
		||||
                            Save
 | 
			
		||||
                            {{ $t("Save") }}
 | 
			
		||||
                        </button>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
 
 | 
			
		||||
@@ -27,18 +27,18 @@ export default {
 | 
			
		||||
 | 
			
		||||
        text() {
 | 
			
		||||
            if (this.status === 0) {
 | 
			
		||||
                return "Down"
 | 
			
		||||
                return this.$t("Down");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (this.status === 1) {
 | 
			
		||||
                return "Up"
 | 
			
		||||
                return this.$t("Up");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (this.status === 2) {
 | 
			
		||||
                return "Pending"
 | 
			
		||||
                return this.$t("Pending");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return "Unknown"
 | 
			
		||||
            return this.$t("Unknown");
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										12
									
								
								src/languages/en.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/languages/en.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
			
		||||
export default {
 | 
			
		||||
    languageName: "English",
 | 
			
		||||
    checkEverySecond: "Check every {0} seconds.",
 | 
			
		||||
    "Avg.": "Avg. ",
 | 
			
		||||
    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.",
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										97
									
								
								src/languages/zh-HK.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								src/languages/zh-HK.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,97 @@
 | 
			
		||||
export default {
 | 
			
		||||
    languageName: "繁體中文 (香港)",
 | 
			
		||||
    Settings: "設定",
 | 
			
		||||
    Dashboard: "錶板",
 | 
			
		||||
    "New Update": "有更新",
 | 
			
		||||
    Language: "語言",
 | 
			
		||||
    Appearance: "外觀",
 | 
			
		||||
    Theme: "主題",
 | 
			
		||||
    General: "一般",
 | 
			
		||||
    Version: "版本",
 | 
			
		||||
    "Check Update On GitHub": "到 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.": "証書期限",
 | 
			
		||||
    days: "日",
 | 
			
		||||
    day: "日",
 | 
			
		||||
    "-day": "日",
 | 
			
		||||
    hour: "小時",
 | 
			
		||||
    "-hour": "小時",
 | 
			
		||||
    checkEverySecond: "每 {0} 秒檢查一次",
 | 
			
		||||
    "Avg.": "平均",
 | 
			
		||||
    Response: "反應時間",
 | 
			
		||||
    Ping: "反應時間",
 | 
			
		||||
    "Monitor Type": "監測器類型",
 | 
			
		||||
    Keyword: "關鍵字",
 | 
			
		||||
    "Friendly Name": "名稱",
 | 
			
		||||
    URL: "網址 URL",
 | 
			
		||||
    Hostname: "Hostname",
 | 
			
		||||
    Port: "Port",
 | 
			
		||||
    "Heartbeat Interval": "檢查間距",
 | 
			
		||||
    Retries: "重試數次確定為離線",
 | 
			
		||||
    retriesDescription: "重試多少次後才判定為離線及傳送通知。如數值為 0 會即判定為離線及傳送通知。",
 | 
			
		||||
    Advanced: "進階",
 | 
			
		||||
    ignoreTLSError: "忽略 TLS/SSL 錯誤",
 | 
			
		||||
    "Upside Down Mode": "反轉模式",
 | 
			
		||||
    upsideDownModeDescription: "反轉狀態,如網址是可正常瀏覽,會被判定為 '離線/DOWN'",
 | 
			
		||||
    "Max. Redirects": "跟隨重新導向 (Redirect) 的次數",
 | 
			
		||||
    maxRedirectDescription: "設為 0 即不跟蹤",
 | 
			
		||||
    "Accepted Status Codes": "接受為上線的 HTTP 狀態碼",
 | 
			
		||||
    acceptedStatusCodesDescription: "可多選",
 | 
			
		||||
    Save: "儲存",
 | 
			
		||||
    Notifications: "通知",
 | 
			
		||||
    "Not available, please setup.": "無法使用,需要設定",
 | 
			
		||||
    "Setup Notification": "設定通知",
 | 
			
		||||
    Light: "明亮",
 | 
			
		||||
    Dark: "暗黑",
 | 
			
		||||
    Auto: "自動",
 | 
			
		||||
    "Theme - Heartbeat Bar": "監測器列表 狀態條外觀",
 | 
			
		||||
    Normal: "一般",
 | 
			
		||||
    Bottom: "下方",
 | 
			
		||||
    None: "沒有",
 | 
			
		||||
    Timezone: "時區",
 | 
			
		||||
    "Search Engine Visibility": "是否允許搜尋器索引",
 | 
			
		||||
    "Allow indexing": "允許索引",
 | 
			
		||||
    "Discourage search engines from indexing site": "不建議搜尋器索引",
 | 
			
		||||
    "Change Password": "變更密碼",
 | 
			
		||||
    "Current Password": "目前密碼",
 | 
			
		||||
    "New Password": "新密碼",
 | 
			
		||||
    "Repeat New Password": "確認新密碼",
 | 
			
		||||
    passwordNotMatchMsg: "密碼不一致",
 | 
			
		||||
    "Update Password": "更新密碼",
 | 
			
		||||
    "Disable Auth": "取消登入認証",
 | 
			
		||||
    "Enable Auth": "開啟登入認証",
 | 
			
		||||
    Logout: "登出",
 | 
			
		||||
    notificationDescription: "新增後,你需要在監測器裡啟用。",
 | 
			
		||||
    Leave: "離開",
 | 
			
		||||
    "I understand, please disable": "我明白,請取消登入認証",
 | 
			
		||||
    Confirm: "確認",
 | 
			
		||||
    Yes: "是",
 | 
			
		||||
    No: "否",
 | 
			
		||||
    Username: "帳號",
 | 
			
		||||
    Password: "密碼",
 | 
			
		||||
    "Remember me": "記住我",
 | 
			
		||||
    Login: "登入",
 | 
			
		||||
    "No Monitors, please": "沒有監測器,請",
 | 
			
		||||
    "add one": "新增",
 | 
			
		||||
    "Notification Type": "通知類型",
 | 
			
		||||
    "Email": "電郵",
 | 
			
		||||
    "Test": "測試",
 | 
			
		||||
}
 | 
			
		||||
@@ -14,18 +14,18 @@
 | 
			
		||||
            </router-link>
 | 
			
		||||
 | 
			
		||||
            <a v-if="hasNewVersion" target="_blank" href="https://github.com/louislam/uptime-kuma/releases" class="btn btn-info me-3">
 | 
			
		||||
                <font-awesome-icon icon="arrow-alt-circle-up" /> New Update
 | 
			
		||||
                <font-awesome-icon icon="arrow-alt-circle-up" /> {{ $t("New Update") }}
 | 
			
		||||
            </a>
 | 
			
		||||
 | 
			
		||||
            <ul class="nav nav-pills">
 | 
			
		||||
                <li class="nav-item">
 | 
			
		||||
                    <router-link to="/dashboard" class="nav-link">
 | 
			
		||||
                        <font-awesome-icon icon="tachometer-alt" /> Dashboard
 | 
			
		||||
                        <font-awesome-icon icon="tachometer-alt" /> {{ $t("Dashboard") }}
 | 
			
		||||
                    </router-link>
 | 
			
		||||
                </li>
 | 
			
		||||
                <li class="nav-item">
 | 
			
		||||
                    <router-link to="/settings" class="nav-link">
 | 
			
		||||
                        <font-awesome-icon icon="cog" /> Settings
 | 
			
		||||
                        <font-awesome-icon icon="cog" /> {{ $t("Settings") }}
 | 
			
		||||
                    </router-link>
 | 
			
		||||
                </li>
 | 
			
		||||
            </ul>
 | 
			
		||||
@@ -48,8 +48,8 @@
 | 
			
		||||
        <footer>
 | 
			
		||||
            <div class="container-fluid">
 | 
			
		||||
                Uptime Kuma -
 | 
			
		||||
                Version: {{ $root.info.version }} -
 | 
			
		||||
                <a href="https://github.com/louislam/uptime-kuma/releases" target="_blank" rel="noopener">Check Update On GitHub</a>
 | 
			
		||||
                {{ $t("Version") }}: {{ $root.info.version }} -
 | 
			
		||||
                <a href="https://github.com/louislam/uptime-kuma/releases" target="_blank" rel="noopener">{{ $t("Check Update On GitHub") }}</a>
 | 
			
		||||
            </div>
 | 
			
		||||
        </footer>
 | 
			
		||||
 | 
			
		||||
@@ -58,22 +58,22 @@
 | 
			
		||||
        <nav v-if="$root.isMobile" class="bottom-nav">
 | 
			
		||||
            <router-link to="/dashboard" class="nav-link">
 | 
			
		||||
                <div><font-awesome-icon icon="tachometer-alt" /></div>
 | 
			
		||||
                Dashboard
 | 
			
		||||
                {{ $t("Dashboard") }}
 | 
			
		||||
            </router-link>
 | 
			
		||||
 | 
			
		||||
            <router-link to="/list" class="nav-link">
 | 
			
		||||
                <div><font-awesome-icon icon="list" /></div>
 | 
			
		||||
                List
 | 
			
		||||
                {{ $t("List") }}
 | 
			
		||||
            </router-link>
 | 
			
		||||
 | 
			
		||||
            <router-link to="/add" class="nav-link">
 | 
			
		||||
                <div><font-awesome-icon icon="plus" /></div>
 | 
			
		||||
                Add
 | 
			
		||||
                {{ $t("Add") }}
 | 
			
		||||
            </router-link>
 | 
			
		||||
 | 
			
		||||
            <router-link to="/settings" class="nav-link">
 | 
			
		||||
                <div><font-awesome-icon icon="cog" /></div>
 | 
			
		||||
                Settings
 | 
			
		||||
                {{ $t("Settings") }}
 | 
			
		||||
            </router-link>
 | 
			
		||||
        </nav>
 | 
			
		||||
    </div>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										20
									
								
								src/main.js
									
									
									
									
									
								
							
							
						
						
									
										20
									
								
								src/main.js
									
									
									
									
									
								
							@@ -1,5 +1,6 @@
 | 
			
		||||
import "bootstrap";
 | 
			
		||||
import { createApp, h } from "vue";
 | 
			
		||||
import { createI18n } from "vue-i18n"
 | 
			
		||||
import { createRouter, createWebHistory } from "vue-router";
 | 
			
		||||
import Toast from "vue-toastification";
 | 
			
		||||
import "vue-toastification/dist/index.css";
 | 
			
		||||
@@ -22,6 +23,9 @@ import List from "./pages/List.vue";
 | 
			
		||||
 | 
			
		||||
import { appName } from "./util.ts";
 | 
			
		||||
 | 
			
		||||
import en from "./languages/en";
 | 
			
		||||
import zhHK from "./languages/zh-HK";
 | 
			
		||||
 | 
			
		||||
const routes = [
 | 
			
		||||
    {
 | 
			
		||||
        path: "/",
 | 
			
		||||
@@ -83,6 +87,19 @@ const router = createRouter({
 | 
			
		||||
    routes,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const languageList = {
 | 
			
		||||
    en,
 | 
			
		||||
    "zh-HK": zhHK,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const i18n = createI18n({
 | 
			
		||||
    locale: localStorage.locale || "en",
 | 
			
		||||
    fallbackLocale: "en",
 | 
			
		||||
    silentFallbackWarn: true,
 | 
			
		||||
    silentTranslationWarn: true,
 | 
			
		||||
    messages: languageList
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const app = createApp({
 | 
			
		||||
    mixins: [
 | 
			
		||||
        socket,
 | 
			
		||||
@@ -98,7 +115,8 @@ const app = createApp({
 | 
			
		||||
    render: () => h(App),
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
app.use(router)
 | 
			
		||||
app.use(router);
 | 
			
		||||
app.use(i18n);
 | 
			
		||||
 | 
			
		||||
const options = {
 | 
			
		||||
    position: "bottom-right",
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,7 @@
 | 
			
		||||
        <div class="row">
 | 
			
		||||
            <div v-if="! $root.isMobile" class="col-12 col-md-5 col-xl-4">
 | 
			
		||||
                <div>
 | 
			
		||||
                    <router-link to="/add" class="btn btn-primary mb-3"><font-awesome-icon icon="plus" /> Add New Monitor</router-link>
 | 
			
		||||
                    <router-link to="/add" class="btn btn-primary mb-3"><font-awesome-icon icon="plus" /> {{ $t("Add New Monitor") }}</router-link>
 | 
			
		||||
                </div>
 | 
			
		||||
                <MonitorList />
 | 
			
		||||
            </div>
 | 
			
		||||
 
 | 
			
		||||
@@ -2,50 +2,38 @@
 | 
			
		||||
    <transition name="slide-fade" appear>
 | 
			
		||||
        <div v-if="$route.name === 'DashboardHome'">
 | 
			
		||||
            <h1 class="mb-3">
 | 
			
		||||
                Quick Stats
 | 
			
		||||
                {{ $t("Quick Stats") }}
 | 
			
		||||
            </h1>
 | 
			
		||||
 | 
			
		||||
            <div class="shadow-box big-padding text-center">
 | 
			
		||||
                <div class="row">
 | 
			
		||||
                    <div class="col">
 | 
			
		||||
                        <h3>Up</h3>
 | 
			
		||||
                        <h3>{{ $t("Up") }}</h3>
 | 
			
		||||
                        <span class="num">{{ stats.up }}</span>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="col">
 | 
			
		||||
                        <h3>Down</h3>
 | 
			
		||||
                        <h3>{{ $t("Down") }}</h3>
 | 
			
		||||
                        <span class="num text-danger">{{ stats.down }}</span>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="col">
 | 
			
		||||
                        <h3>Unknown</h3>
 | 
			
		||||
                        <h3>{{ $t("Unknown") }}</h3>
 | 
			
		||||
                        <span class="num text-secondary">{{ stats.unknown }}</span>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="col">
 | 
			
		||||
                        <h3>Pause</h3>
 | 
			
		||||
                        <h3>{{ $t("Pause") }}</h3>
 | 
			
		||||
                        <span class="num text-secondary">{{ stats.pause }}</span>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div v-if="false" class="row">
 | 
			
		||||
                    <div class="col-3">
 | 
			
		||||
                        <h3>Uptime</h3>
 | 
			
		||||
                        <p>(24-hour)</p>
 | 
			
		||||
                        <span class="num" />
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="col-3">
 | 
			
		||||
                        <h3>Uptime</h3>
 | 
			
		||||
                        <p>(30-day)</p>
 | 
			
		||||
                        <span class="num" />
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <div class="shadow-box table-shadow-box" style="overflow-x: scroll">
 | 
			
		||||
                <table class="table table-borderless table-hover">
 | 
			
		||||
                    <thead>
 | 
			
		||||
                        <tr>
 | 
			
		||||
                            <th>Name</th>
 | 
			
		||||
                            <th>Status</th>
 | 
			
		||||
                            <th>DateTime</th>
 | 
			
		||||
                            <th>Message</th>
 | 
			
		||||
                            <th>{{ $t("Name") }}</th>
 | 
			
		||||
                            <th>{{ $t("Status") }}</th>
 | 
			
		||||
                            <th>{{ $t("DateTime") }}</th>
 | 
			
		||||
                            <th>{{ $t("Message") }}</th>
 | 
			
		||||
                        </tr>
 | 
			
		||||
                    </thead>
 | 
			
		||||
                    <tbody>
 | 
			
		||||
@@ -58,7 +46,7 @@
 | 
			
		||||
 | 
			
		||||
                        <tr v-if="importantHeartBeatList.length === 0">
 | 
			
		||||
                            <td colspan="4">
 | 
			
		||||
                                No important events
 | 
			
		||||
                                {{ $t("No important events") }}
 | 
			
		||||
                            </td>
 | 
			
		||||
                        </tr>
 | 
			
		||||
                    </tbody>
 | 
			
		||||
 
 | 
			
		||||
@@ -14,16 +14,16 @@
 | 
			
		||||
 | 
			
		||||
            <div class="functions">
 | 
			
		||||
                <button v-if="monitor.active" class="btn btn-light" @click="pauseDialog">
 | 
			
		||||
                    <font-awesome-icon icon="pause" /> Pause
 | 
			
		||||
                    <font-awesome-icon icon="pause" /> {{ $t("Pause") }}
 | 
			
		||||
                </button>
 | 
			
		||||
                <button v-if="! monitor.active" class="btn btn-primary" @click="resumeMonitor">
 | 
			
		||||
                    <font-awesome-icon icon="play" /> Resume
 | 
			
		||||
                    <font-awesome-icon icon="play" /> {{ $t("Resume") }}
 | 
			
		||||
                </button>
 | 
			
		||||
                <router-link :to=" '/edit/' + monitor.id " class="btn btn-secondary">
 | 
			
		||||
                    <font-awesome-icon icon="edit" /> Edit
 | 
			
		||||
                    <font-awesome-icon icon="edit" /> {{ $t("Edit") }}
 | 
			
		||||
                </router-link>
 | 
			
		||||
                <button class="btn btn-danger" @click="deleteDialog">
 | 
			
		||||
                    <font-awesome-icon icon="trash" /> Delete
 | 
			
		||||
                    <font-awesome-icon icon="trash" /> {{ $t("Delete") }}
 | 
			
		||||
                </button>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
@@ -31,7 +31,7 @@
 | 
			
		||||
                <div class="row">
 | 
			
		||||
                    <div class="col-md-8">
 | 
			
		||||
                        <HeartbeatBar :monitor-id="monitor.id" />
 | 
			
		||||
                        <span class="word">Check every {{ monitor.interval }} seconds.</span>
 | 
			
		||||
                        <span class="word">{{ $t("checkEverySecond", [ monitor.interval ]) }}</span>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="col-md-4 text-center">
 | 
			
		||||
                        <span class="badge rounded-pill" :class=" 'bg-' + status.color " style="font-size: 30px">{{ status.text }}</span>
 | 
			
		||||
@@ -43,7 +43,7 @@
 | 
			
		||||
                <div class="row">
 | 
			
		||||
                    <div class="col">
 | 
			
		||||
                        <h4>{{ pingTitle }}</h4>
 | 
			
		||||
                        <p>(Current)</p>
 | 
			
		||||
                        <p>({{ $t("Current") }})</p>
 | 
			
		||||
                        <span class="num">
 | 
			
		||||
                            <a href="#" @click.prevent="showPingChartBox = !showPingChartBox">
 | 
			
		||||
                                <CountUp :value="ping" />
 | 
			
		||||
@@ -51,26 +51,26 @@
 | 
			
		||||
                        </span>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="col">
 | 
			
		||||
                        <h4>Avg. {{ pingTitle }}</h4>
 | 
			
		||||
                        <p>(24-hour)</p>
 | 
			
		||||
                        <h4>{{ $t("Avg.") }}{{ pingTitle }}</h4>
 | 
			
		||||
                        <p>(24{{ $t("-hour") }})</p>
 | 
			
		||||
                        <span class="num"><CountUp :value="avgPing" /></span>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="col">
 | 
			
		||||
                        <h4>Uptime</h4>
 | 
			
		||||
                        <p>(24-hour)</p>
 | 
			
		||||
                        <h4>{{ $t("Uptime") }}</h4>
 | 
			
		||||
                        <p>(24{{ $t("-hour") }})</p>
 | 
			
		||||
                        <span class="num"><Uptime :monitor="monitor" type="24" /></span>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="col">
 | 
			
		||||
                        <h4>Uptime</h4>
 | 
			
		||||
                        <p>(30-day)</p>
 | 
			
		||||
                        <h4>{{ $t("Uptime") }}</h4>
 | 
			
		||||
                        <p>(30{{ $t("-day") }})</p>
 | 
			
		||||
                        <span class="num"><Uptime :monitor="monitor" type="720" /></span>
 | 
			
		||||
                    </div>
 | 
			
		||||
 | 
			
		||||
                    <div v-if="certInfo" class="col">
 | 
			
		||||
                        <h4>Cert Exp.</h4>
 | 
			
		||||
                        <h4>{{ $t("Cert Exp.") }}</h4>
 | 
			
		||||
                        <p>(<Datetime :value="certInfo.validTo" date-only />)</p>
 | 
			
		||||
                        <span class="num">
 | 
			
		||||
                            <a href="#" @click.prevent="toggleCertInfoBox = !toggleCertInfoBox">{{ certInfo.daysRemaining }} days</a>
 | 
			
		||||
                            <a href="#" @click.prevent="toggleCertInfoBox = !toggleCertInfoBox">{{ certInfo.daysRemaining }} {{ $t("days") }}</a>
 | 
			
		||||
                        </span>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
@@ -132,9 +132,9 @@
 | 
			
		||||
                <table class="table table-borderless table-hover">
 | 
			
		||||
                    <thead>
 | 
			
		||||
                        <tr>
 | 
			
		||||
                            <th>Status</th>
 | 
			
		||||
                            <th>DateTime</th>
 | 
			
		||||
                            <th>Message</th>
 | 
			
		||||
                            <th>{{ $t("Status") }}</th>
 | 
			
		||||
                            <th>{{ $t("DateTime") }}</th>
 | 
			
		||||
                            <th>{{ $t("Message") }}</th>
 | 
			
		||||
                        </tr>
 | 
			
		||||
                    </thead>
 | 
			
		||||
                    <tbody>
 | 
			
		||||
@@ -146,7 +146,7 @@
 | 
			
		||||
 | 
			
		||||
                        <tr v-if="importantHeartBeatList.length === 0">
 | 
			
		||||
                            <td colspan="3">
 | 
			
		||||
                                No important events
 | 
			
		||||
                                {{ $t("No important events") }}
 | 
			
		||||
                            </td>
 | 
			
		||||
                        </tr>
 | 
			
		||||
                    </tbody>
 | 
			
		||||
@@ -209,10 +209,9 @@ export default {
 | 
			
		||||
 | 
			
		||||
        pingTitle() {
 | 
			
		||||
            if (this.monitor.type === "http") {
 | 
			
		||||
                return "Response"
 | 
			
		||||
                return this.$t("Response");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return "Ping"
 | 
			
		||||
            return this.$t("Ping");
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        monitor() {
 | 
			
		||||
 
 | 
			
		||||
@@ -6,10 +6,10 @@
 | 
			
		||||
                <div class="shadow-box">
 | 
			
		||||
                    <div class="row">
 | 
			
		||||
                        <div class="col-md-6">
 | 
			
		||||
                            <h2 class="mb-2">General</h2>
 | 
			
		||||
                            <h2 class="mb-2">{{ $t("General") }}</h2>
 | 
			
		||||
 | 
			
		||||
                            <div class="my-3">
 | 
			
		||||
                                <label for="type" class="form-label">Monitor Type</label>
 | 
			
		||||
                                <label for="type" class="form-label">{{ $t("Monitor Type") }}</label>
 | 
			
		||||
                                <select id="type" v-model="monitor.type" class="form-select" aria-label="Default select example">
 | 
			
		||||
                                    <option value="http">
 | 
			
		||||
                                        HTTP(s)
 | 
			
		||||
@@ -21,23 +21,23 @@
 | 
			
		||||
                                        Ping
 | 
			
		||||
                                    </option>
 | 
			
		||||
                                    <option value="keyword">
 | 
			
		||||
                                        HTTP(s) - Keyword
 | 
			
		||||
                                        HTTP(s) - {{ $t("Keyword") }}
 | 
			
		||||
                                    </option>
 | 
			
		||||
                                </select>
 | 
			
		||||
                            </div>
 | 
			
		||||
 | 
			
		||||
                            <div class="my-3">
 | 
			
		||||
                                <label for="name" class="form-label">Friendly Name</label>
 | 
			
		||||
                                <label for="name" class="form-label">{{ $t("Friendly Name") }}</label>
 | 
			
		||||
                                <input id="name" v-model="monitor.name" type="text" class="form-control" required>
 | 
			
		||||
                            </div>
 | 
			
		||||
 | 
			
		||||
                            <div v-if="monitor.type === 'http' || monitor.type === 'keyword' " class="my-3">
 | 
			
		||||
                                <label for="url" class="form-label">URL</label>
 | 
			
		||||
                                <label for="url" class="form-label">{{ $t("URL") }}</label>
 | 
			
		||||
                                <input id="url" v-model="monitor.url" type="url" class="form-control" pattern="https?://.+" required>
 | 
			
		||||
                            </div>
 | 
			
		||||
 | 
			
		||||
                            <div v-if="monitor.type === 'keyword' " class="my-3">
 | 
			
		||||
                                <label for="keyword" class="form-label">Keyword</label>
 | 
			
		||||
                                <label for="keyword" class="form-label">{{ $t("Keyword") }}</label>
 | 
			
		||||
                                <input id="keyword" v-model="monitor.keyword" type="text" class="form-control" required>
 | 
			
		||||
                                <div class="form-text">
 | 
			
		||||
                                    Search keyword in plain html or JSON response and it is case-sensitive
 | 
			
		||||
@@ -45,57 +45,57 @@
 | 
			
		||||
                            </div>
 | 
			
		||||
 | 
			
		||||
                            <div v-if="monitor.type === 'port' || monitor.type === 'ping' " class="my-3">
 | 
			
		||||
                                <label for="hostname" class="form-label">Hostname</label>
 | 
			
		||||
                                <label for="hostname" class="form-label">{{ $t("Hostname") }}</label>
 | 
			
		||||
                                <input id="hostname" v-model="monitor.hostname" type="text" class="form-control" required>
 | 
			
		||||
                            </div>
 | 
			
		||||
 | 
			
		||||
                            <div v-if="monitor.type === 'port' " class="my-3">
 | 
			
		||||
                                <label for="port" class="form-label">Port</label>
 | 
			
		||||
                                <label for="port" class="form-label">{{ $t("Port") }}</label>
 | 
			
		||||
                                <input id="port" v-model="monitor.port" type="number" class="form-control" required min="0" max="65535" step="1">
 | 
			
		||||
                            </div>
 | 
			
		||||
 | 
			
		||||
                            <div class="my-3">
 | 
			
		||||
                                <label for="interval" class="form-label">Heartbeat Interval (Every {{ monitor.interval }} seconds)</label>
 | 
			
		||||
                                <label for="interval" class="form-label">{{ $t("Heartbeat Interval") }} ({{ $t("checkEverySecond", [ monitor.interval ]) }})</label>
 | 
			
		||||
                                <input id="interval" v-model="monitor.interval" type="number" class="form-control" required min="20" step="1">
 | 
			
		||||
                            </div>
 | 
			
		||||
 | 
			
		||||
                            <div class="my-3">
 | 
			
		||||
                                <label for="maxRetries" class="form-label">Retries</label>
 | 
			
		||||
                                <label for="maxRetries" class="form-label">{{ $t("Retries") }}</label>
 | 
			
		||||
                                <input id="maxRetries" v-model="monitor.maxretries" type="number" class="form-control" required min="0" step="1">
 | 
			
		||||
                                <div class="form-text">
 | 
			
		||||
                                    Maximum retries before the service is marked as down and a notification is sent
 | 
			
		||||
                                    {{ $t("retriesDescription") }}
 | 
			
		||||
                                </div>
 | 
			
		||||
                            </div>
 | 
			
		||||
 | 
			
		||||
                            <h2 class="mt-5 mb-2">Advanced</h2>
 | 
			
		||||
                            <h2 class="mt-5 mb-2">{{ $t("Advanced") }}</h2>
 | 
			
		||||
 | 
			
		||||
                            <div v-if="monitor.type === 'http' || monitor.type === 'keyword' " class="my-3 form-check">
 | 
			
		||||
                                <input id="ignore-tls" v-model="monitor.ignoreTls" class="form-check-input" type="checkbox" value="">
 | 
			
		||||
                                <label class="form-check-label" for="ignore-tls">
 | 
			
		||||
                                    Ignore TLS/SSL error for HTTPS websites
 | 
			
		||||
                                    {{ $t("ignoreTLSError") }}
 | 
			
		||||
                                </label>
 | 
			
		||||
                            </div>
 | 
			
		||||
 | 
			
		||||
                            <div class="my-3 form-check">
 | 
			
		||||
                                <input id="upside-down" v-model="monitor.upsideDown" class="form-check-input" type="checkbox">
 | 
			
		||||
                                <label class="form-check-label" for="upside-down">
 | 
			
		||||
                                    Upside Down Mode
 | 
			
		||||
                                    {{ $t("Upside Down Mode") }}
 | 
			
		||||
                                </label>
 | 
			
		||||
                                <div class="form-text">
 | 
			
		||||
                                    Flip the status upside down. If the service is reachable, it is DOWN.
 | 
			
		||||
                                    {{ $t("upsideDownModeDescription") }}
 | 
			
		||||
                                </div>
 | 
			
		||||
                            </div>
 | 
			
		||||
 | 
			
		||||
                            <div v-if="monitor.type === 'http' || monitor.type === 'keyword' " class="my-3">
 | 
			
		||||
                                <label for="maxRedirects" class="form-label">Max. Redirects</label>
 | 
			
		||||
                                <label for="maxRedirects" class="form-label">{{ $t("Max. Redirects") }}</label>
 | 
			
		||||
                                <input id="maxRedirects" v-model="monitor.maxredirects" type="number" class="form-control" required min="0" step="1">
 | 
			
		||||
                                <div class="form-text">
 | 
			
		||||
                                    Maximum number of redirects to follow. Set to 0 to disable redirects.
 | 
			
		||||
                                    {{ $t("maxRedirectDescription") }}
 | 
			
		||||
                                </div>
 | 
			
		||||
                            </div>
 | 
			
		||||
 | 
			
		||||
                            <div v-if="monitor.type === 'http' || monitor.type === 'keyword' " class="my-3">
 | 
			
		||||
                                <label for="acceptedStatusCodes" class="form-label">Accepted Status Codes</label>
 | 
			
		||||
                                <label for="acceptedStatusCodes" class="form-label">{{ $t("Accepted Status Codes") }}</label>
 | 
			
		||||
 | 
			
		||||
                                <VueMultiselect
 | 
			
		||||
                                    id="acceptedStatusCodes"
 | 
			
		||||
@@ -112,21 +112,21 @@
 | 
			
		||||
                                ></VueMultiselect>
 | 
			
		||||
 | 
			
		||||
                                <div class="form-text">
 | 
			
		||||
                                    Select status codes which are considered as a successful response.
 | 
			
		||||
                                    {{ $t("acceptedStatusCodesDescription") }}
 | 
			
		||||
                                </div>
 | 
			
		||||
                            </div>
 | 
			
		||||
 | 
			
		||||
                            <div class="mt-5 mb-1">
 | 
			
		||||
                                <button class="btn btn-primary" type="submit" :disabled="processing">Save</button>
 | 
			
		||||
                                <button class="btn btn-primary" type="submit" :disabled="processing">{{ $t("Save") }}</button>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
 | 
			
		||||
                        <div class="col-md-6">
 | 
			
		||||
                            <div v-if="$root.isMobile" class="mt-3" />
 | 
			
		||||
 | 
			
		||||
                            <h2 class="mb-2">Notifications</h2>
 | 
			
		||||
                            <h2 class="mb-2">{{ $t("Notifications") }}</h2>
 | 
			
		||||
                            <p v-if="$root.notificationList.length === 0">
 | 
			
		||||
                                Not available, please setup.
 | 
			
		||||
                                {{ $t("Not available, please setup.") }}
 | 
			
		||||
                            </p>
 | 
			
		||||
 | 
			
		||||
                            <div v-for="notification in $root.notificationList" :key="notification.id" class="form-check form-switch my-3">
 | 
			
		||||
@@ -134,12 +134,12 @@
 | 
			
		||||
 | 
			
		||||
                                <label class="form-check-label" :for=" 'notification' + notification.id">
 | 
			
		||||
                                    {{ notification.name }}
 | 
			
		||||
                                    <a href="#" @click="$refs.notificationDialog.show(notification.id)">Edit</a>
 | 
			
		||||
                                    <a href="#" @click="$refs.notificationDialog.show(notification.id)">{{ $t("Edit") }}</a>
 | 
			
		||||
                                </label>
 | 
			
		||||
                            </div>
 | 
			
		||||
 | 
			
		||||
                            <button class="btn btn-primary me-2" type="button" @click="$refs.notificationDialog.show()">
 | 
			
		||||
                                Setup Notification
 | 
			
		||||
                                {{ $t("Setup Notification") }}
 | 
			
		||||
                            </button>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
@@ -175,7 +175,7 @@ export default {
 | 
			
		||||
 | 
			
		||||
    computed: {
 | 
			
		||||
        pageName() {
 | 
			
		||||
            return (this.isAdd) ? "Add New Monitor" : "Edit"
 | 
			
		||||
            return this.$t((this.isAdd) ? "Add New Monitor" : "Edit");
 | 
			
		||||
        },
 | 
			
		||||
        isAdd() {
 | 
			
		||||
            return this.$route.path === "/add";
 | 
			
		||||
 
 | 
			
		||||
@@ -2,53 +2,63 @@
 | 
			
		||||
    <transition name="slide-fade" appear>
 | 
			
		||||
        <div>
 | 
			
		||||
            <h1 v-show="show" class="mb-3">
 | 
			
		||||
                Settings
 | 
			
		||||
                {{ $t("Settings") }}
 | 
			
		||||
            </h1>
 | 
			
		||||
 | 
			
		||||
            <div class="shadow-box">
 | 
			
		||||
                <div class="row">
 | 
			
		||||
                    <div class="col-md-6">
 | 
			
		||||
                        <h2 class="mb-2">General</h2>
 | 
			
		||||
                        <h2 class="mb-2">{{ $t("Appearance") }}</h2>
 | 
			
		||||
 | 
			
		||||
                        <div class="mb-3">
 | 
			
		||||
                            <label for="language" class="form-label">{{ $t("Language") }}</label>
 | 
			
		||||
                            <select id="language" v-model="$i18n.locale" class="form-select">
 | 
			
		||||
                                <option v-for="(lang, i) in $i18n.availableLocales" :key="`Lang${i}`" :value="lang">
 | 
			
		||||
                                    {{ $i18n.messages[lang].languageName }}
 | 
			
		||||
                                </option>
 | 
			
		||||
                            </select>
 | 
			
		||||
                        </div>
 | 
			
		||||
 | 
			
		||||
                        <div class="mb-3">
 | 
			
		||||
                            <label for="timezone" class="form-label">{{ $t("Theme") }}</label>
 | 
			
		||||
 | 
			
		||||
                            <div>
 | 
			
		||||
                                <div class="btn-group" role="group" aria-label="Basic checkbox toggle button group">
 | 
			
		||||
                                    <input id="btncheck1" v-model="$root.userTheme" type="radio" class="btn-check" name="theme" autocomplete="off" value="light">
 | 
			
		||||
                                    <label class="btn btn-outline-primary" for="btncheck1">{{ $t("Light") }}</label>
 | 
			
		||||
 | 
			
		||||
                                    <input id="btncheck2" v-model="$root.userTheme" type="radio" class="btn-check" name="theme" autocomplete="off" value="dark">
 | 
			
		||||
                                    <label class="btn btn-outline-primary" for="btncheck2">{{ $t("Dark") }}</label>
 | 
			
		||||
 | 
			
		||||
                                    <input id="btncheck3" v-model="$root.userTheme" type="radio" class="btn-check" name="theme" autocomplete="off" value="auto">
 | 
			
		||||
                                    <label class="btn btn-outline-primary" for="btncheck3">{{ $t("Auto") }}</label>
 | 
			
		||||
                                </div>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
 | 
			
		||||
                        <div class="mb-3">
 | 
			
		||||
                            <label class="form-label">{{ $t("Theme - Heartbeat Bar") }}</label>
 | 
			
		||||
                            <div>
 | 
			
		||||
                                <div class="btn-group" role="group" aria-label="Basic checkbox toggle button group">
 | 
			
		||||
                                    <input id="btncheck4" v-model="$root.userHeartbeatBar" type="radio" class="btn-check" name="heartbeatBarTheme" autocomplete="off" value="normal">
 | 
			
		||||
                                    <label class="btn btn-outline-primary" for="btncheck4">{{ $t("Normal") }}</label>
 | 
			
		||||
 | 
			
		||||
                                    <input id="btncheck5" v-model="$root.userHeartbeatBar" type="radio" class="btn-check" name="heartbeatBarTheme" autocomplete="off" value="bottom">
 | 
			
		||||
                                    <label class="btn btn-outline-primary" for="btncheck5">{{ $t("Bottom") }}</label>
 | 
			
		||||
 | 
			
		||||
                                    <input id="btncheck6" v-model="$root.userHeartbeatBar" type="radio" class="btn-check" name="heartbeatBarTheme" autocomplete="off" value="none">
 | 
			
		||||
                                    <label class="btn btn-outline-primary" for="btncheck6">{{ $t("None") }}</label>
 | 
			
		||||
                                </div>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
 | 
			
		||||
                        <h2 class="mt-5 mb-2">{{ $t("General") }}</h2>
 | 
			
		||||
                        <form class="mb-3" @submit.prevent="saveGeneral">
 | 
			
		||||
                            <div class="mb-3">
 | 
			
		||||
                                <label for="timezone" class="form-label">Theme</label>
 | 
			
		||||
 | 
			
		||||
                                <div>
 | 
			
		||||
                                    <div class="btn-group" role="group" aria-label="Basic checkbox toggle button group">
 | 
			
		||||
                                        <input id="btncheck1" v-model="$root.userTheme" type="radio" class="btn-check" name="theme" autocomplete="off" value="light">
 | 
			
		||||
                                        <label class="btn btn-outline-primary" for="btncheck1">Light</label>
 | 
			
		||||
 | 
			
		||||
                                        <input id="btncheck2" v-model="$root.userTheme" type="radio" class="btn-check" name="theme" autocomplete="off" value="dark">
 | 
			
		||||
                                        <label class="btn btn-outline-primary" for="btncheck2">Dark</label>
 | 
			
		||||
 | 
			
		||||
                                        <input id="btncheck3" v-model="$root.userTheme" type="radio" class="btn-check" name="theme" autocomplete="off" value="auto">
 | 
			
		||||
                                        <label class="btn btn-outline-primary" for="btncheck3">Auto</label>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                </div>
 | 
			
		||||
                            </div>
 | 
			
		||||
 | 
			
		||||
                            <div class="mb-3">
 | 
			
		||||
                                <label class="form-label">Theme - Heartbeat Bar</label>
 | 
			
		||||
                                <div>
 | 
			
		||||
                                    <div class="btn-group" role="group" aria-label="Basic checkbox toggle button group">
 | 
			
		||||
                                        <input id="btncheck4" v-model="$root.userHeartbeatBar" type="radio" class="btn-check" name="heartbeatBarTheme" autocomplete="off" value="normal">
 | 
			
		||||
                                        <label class="btn btn-outline-primary" for="btncheck4">Normal</label>
 | 
			
		||||
 | 
			
		||||
                                        <input id="btncheck5" v-model="$root.userHeartbeatBar" type="radio" class="btn-check" name="heartbeatBarTheme" autocomplete="off" value="bottom">
 | 
			
		||||
                                        <label class="btn btn-outline-primary" for="btncheck5">Bottom</label>
 | 
			
		||||
 | 
			
		||||
                                        <input id="btncheck6" v-model="$root.userHeartbeatBar" type="radio" class="btn-check" name="heartbeatBarTheme" autocomplete="off" value="none">
 | 
			
		||||
                                        <label class="btn btn-outline-primary" for="btncheck6">None</label>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                </div>
 | 
			
		||||
                            </div>
 | 
			
		||||
 | 
			
		||||
                            <div class="mb-3">
 | 
			
		||||
                                <label for="timezone" class="form-label">Timezone</label>
 | 
			
		||||
                                <label for="timezone" class="form-label">{{ $t("Timezone") }}</label>
 | 
			
		||||
                                <select id="timezone" v-model="$root.userTimezone" class="form-select">
 | 
			
		||||
                                    <option value="auto">
 | 
			
		||||
                                        Auto: {{ guessTimezone }}
 | 
			
		||||
                                        {{ $t("Auto") }}: {{ guessTimezone }}
 | 
			
		||||
                                    </option>
 | 
			
		||||
                                    <option v-for="(timezone, index) in timezoneList" :key="index" :value="timezone.value">
 | 
			
		||||
                                        {{ timezone.name }}
 | 
			
		||||
@@ -57,65 +67,65 @@
 | 
			
		||||
                            </div>
 | 
			
		||||
 | 
			
		||||
                            <div class="mb-3">
 | 
			
		||||
                                <label class="form-label">Search Engine Visibility</label>
 | 
			
		||||
                                <label class="form-label">{{ $t("Search Engine Visibility") }}</label>
 | 
			
		||||
 | 
			
		||||
                                <div class="form-check">
 | 
			
		||||
                                    <input id="searchEngineIndexYes" v-model="settings.searchEngineIndex" class="form-check-input" type="radio" name="flexRadioDefault" :value="true" required>
 | 
			
		||||
                                    <label class="form-check-label" for="searchEngineIndexYes">
 | 
			
		||||
                                        Allow indexing
 | 
			
		||||
                                        {{ $t("Allow indexing") }}
 | 
			
		||||
                                    </label>
 | 
			
		||||
                                </div>
 | 
			
		||||
                                <div class="form-check">
 | 
			
		||||
                                    <input id="searchEngineIndexNo" v-model="settings.searchEngineIndex" class="form-check-input" type="radio" name="flexRadioDefault" :value="false" required>
 | 
			
		||||
                                    <label class="form-check-label" for="searchEngineIndexNo">
 | 
			
		||||
                                        Discourage search engines from indexing site
 | 
			
		||||
                                        {{ $t("Discourage search engines from indexing site") }}
 | 
			
		||||
                                    </label>
 | 
			
		||||
                                </div>
 | 
			
		||||
                            </div>
 | 
			
		||||
 | 
			
		||||
                            <div>
 | 
			
		||||
                                <button class="btn btn-primary" type="submit">
 | 
			
		||||
                                    Save
 | 
			
		||||
                                    {{ $t("Save") }}
 | 
			
		||||
                                </button>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </form>
 | 
			
		||||
 | 
			
		||||
                        <template v-if="loaded">
 | 
			
		||||
                            <template v-if="! settings.disableAuth">
 | 
			
		||||
                                <h2 class="mt-5 mb-2">Change Password</h2>
 | 
			
		||||
                                <h2 class="mt-5 mb-2">{{ $t("Change Password") }}</h2>
 | 
			
		||||
                                <form class="mb-3" @submit.prevent="savePassword">
 | 
			
		||||
                                    <div class="mb-3">
 | 
			
		||||
                                        <label for="current-password" class="form-label">Current Password</label>
 | 
			
		||||
                                        <label for="current-password" class="form-label">{{ $t("Current Password") }}</label>
 | 
			
		||||
                                        <input id="current-password" v-model="password.currentPassword" type="password" class="form-control" required>
 | 
			
		||||
                                    </div>
 | 
			
		||||
 | 
			
		||||
                                    <div class="mb-3">
 | 
			
		||||
                                        <label for="new-password" class="form-label">New Password</label>
 | 
			
		||||
                                        <label for="new-password" class="form-label">{{ $t("New Password") }}</label>
 | 
			
		||||
                                        <input id="new-password" v-model="password.newPassword" type="password" class="form-control" required>
 | 
			
		||||
                                    </div>
 | 
			
		||||
 | 
			
		||||
                                    <div class="mb-3">
 | 
			
		||||
                                        <label for="repeat-new-password" class="form-label">Repeat New Password</label>
 | 
			
		||||
                                        <label for="repeat-new-password" class="form-label">{{ $t("Repeat New Password") }}</label>
 | 
			
		||||
                                        <input id="repeat-new-password" v-model="password.repeatNewPassword" type="password" class="form-control" :class="{ 'is-invalid' : invalidPassword }" required>
 | 
			
		||||
                                        <div class="invalid-feedback">
 | 
			
		||||
                                            The repeat password does not match.
 | 
			
		||||
                                            {{ $t("passwordNotMatchMsg") }}
 | 
			
		||||
                                        </div>
 | 
			
		||||
                                    </div>
 | 
			
		||||
 | 
			
		||||
                                    <div>
 | 
			
		||||
                                        <button class="btn btn-primary" type="submit">
 | 
			
		||||
                                            Update Password
 | 
			
		||||
                                            {{ $t("Update Password") }}
 | 
			
		||||
                                        </button>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                </form>
 | 
			
		||||
                            </template>
 | 
			
		||||
 | 
			
		||||
                            <h2 class="mt-5 mb-2">Advanced</h2>
 | 
			
		||||
                            <h2 class="mt-5 mb-2">{{ $t("Advanced") }}</h2>
 | 
			
		||||
 | 
			
		||||
                            <div class="mb-3">
 | 
			
		||||
                                <button v-if="settings.disableAuth" class="btn btn-outline-primary me-1" @click="enableAuth">Enable Auth</button>
 | 
			
		||||
                                <button v-if="! settings.disableAuth" class="btn btn-primary me-1" @click="confirmDisableAuth">Disable Auth</button>
 | 
			
		||||
                                <button v-if="! settings.disableAuth" class="btn btn-danger me-1" @click="$root.logout">Logout</button>
 | 
			
		||||
                                <button v-if="settings.disableAuth" class="btn btn-outline-primary me-1" @click="enableAuth">{{ $t("Enable Auth") }}</button>
 | 
			
		||||
                                <button v-if="! settings.disableAuth" class="btn btn-primary me-1" @click="confirmDisableAuth">{{ $t("Disable Auth") }}</button>
 | 
			
		||||
                                <button v-if="! settings.disableAuth" class="btn btn-danger me-1" @click="$root.logout">{{ $t("Logout") }}</button>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </template>
 | 
			
		||||
                    </div>
 | 
			
		||||
@@ -123,23 +133,23 @@
 | 
			
		||||
                    <div class="notification-list col-md-6">
 | 
			
		||||
                        <div v-if="$root.isMobile" class="mt-3" />
 | 
			
		||||
 | 
			
		||||
                        <h2>Notifications</h2>
 | 
			
		||||
                        <h2>{{ $t("Notifications") }}</h2>
 | 
			
		||||
                        <p v-if="$root.notificationList.length === 0">
 | 
			
		||||
                            Not available, please setup.
 | 
			
		||||
                            {{ $t("Not available, please setup.") }}
 | 
			
		||||
                        </p>
 | 
			
		||||
                        <p v-else>
 | 
			
		||||
                            Please assign a notification to monitor(s) to get it to work.
 | 
			
		||||
                            {{ $t("notificationDescription") }}
 | 
			
		||||
                        </p>
 | 
			
		||||
 | 
			
		||||
                        <ul class="list-group mb-3" style="border-radius: 1rem;">
 | 
			
		||||
                            <li v-for="(notification, index) in $root.notificationList" :key="index" class="list-group-item">
 | 
			
		||||
                                {{ notification.name }}<br>
 | 
			
		||||
                                <a href="#" @click="$refs.notificationDialog.show(notification.id)">Edit</a>
 | 
			
		||||
                                <a href="#" @click="$refs.notificationDialog.show(notification.id)">{{ $t("Edit") }}</a>
 | 
			
		||||
                            </li>
 | 
			
		||||
                        </ul>
 | 
			
		||||
 | 
			
		||||
                        <button class="btn btn-primary me-2" type="button" @click="$refs.notificationDialog.show()">
 | 
			
		||||
                            Setup Notification
 | 
			
		||||
                            {{ $t("Setup Notification") }}
 | 
			
		||||
                        </button>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
@@ -147,10 +157,18 @@
 | 
			
		||||
 | 
			
		||||
            <NotificationDialog ref="notificationDialog" />
 | 
			
		||||
 | 
			
		||||
            <Confirm ref="confirmDisableAuth" btn-style="btn-danger" yes-text="I understand, please disable" no-text="Leave" @yes="disableAuth">
 | 
			
		||||
                <p>Are you sure want to <strong>disable auth</strong>?</p>
 | 
			
		||||
                <p>It is for <strong>someone who have 3rd-party auth</strong> in front of  Uptime Kuma such as Cloudflare Access.</p>
 | 
			
		||||
                <p>Please use it carefully.</p>
 | 
			
		||||
            <Confirm ref="confirmDisableAuth" btn-style="btn-danger" :yes-text="$t('I understand, please disable')" :no-text="$t('Leave')" @yes="disableAuth">
 | 
			
		||||
                <template v-if="$i18n.locale === 'en' ">
 | 
			
		||||
                    <p>Are you sure want to <strong>disable auth</strong>?</p>
 | 
			
		||||
                    <p>It is for <strong>someone who have 3rd-party auth</strong> in front of Uptime Kuma such as Cloudflare Access.</p>
 | 
			
		||||
                    <p>Please use it carefully.</p>
 | 
			
		||||
                </template>
 | 
			
		||||
 | 
			
		||||
                <template v-if="$i18n.locale === 'zh-HK' ">
 | 
			
		||||
                    <p>你是否確認<strong>取消登入認証</strong>?</p>
 | 
			
		||||
                    <p>這個功能是設計給已有<strong>第三方認証</strong>的用家,例如 Cloudflare Access。</p>
 | 
			
		||||
                    <p>請小心使用。</p>
 | 
			
		||||
                </template>
 | 
			
		||||
            </Confirm>
 | 
			
		||||
        </div>
 | 
			
		||||
    </transition>
 | 
			
		||||
@@ -195,6 +213,10 @@ export default {
 | 
			
		||||
        "password.repeatNewPassword"() {
 | 
			
		||||
            this.invalidPassword = false;
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        "$i18n.locale"() {
 | 
			
		||||
            localStorage.locale = this.$i18n.locale;
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    mounted() {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user