diff --git a/package-lock.json b/package-lock.json index 98602ef0..778e6bc3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "badge-maker": "^3.3.1", "bcryptjs": "~2.4.3", "bree": "~7.1.5", + "cacheable-lookup": "~6.0.4", "chardet": "^1.3.0", "check-password-strength": "^2.0.5", "cheerio": "^1.0.0-rc.10", @@ -41,12 +42,14 @@ "nodemailer": "~6.6.5", "notp": "~2.0.3", "password-hash": "~1.2.2", + "pg": "^8.7.3", + "pg-connection-string": "^2.5.0", "prom-client": "~13.2.0", "prometheus-api-metrics": "~3.2.1", "redbean-node": "0.1.4", "socket.io": "~4.4.1", "socket.io-client": "~4.4.1", - "socks-proxy-agent": "^6.1.1", + "socks-proxy-agent": "6.1.1", "tar": "^6.1.11", "tcp-ping": "~0.1.1", "thirty-two": "~1.0.2" @@ -4788,6 +4791,14 @@ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" }, + "node_modules/buffer-writer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", + "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==", + "engines": { + "node": ">=4" + } + }, "node_modules/bulk-write-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/bulk-write-stream/-/bulk-write-stream-2.0.1.tgz", @@ -4806,6 +4817,14 @@ "node": ">= 0.8" } }, + "node_modules/cacheable-lookup": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-6.0.4.tgz", + "integrity": "sha512-mbcDEZCkv2CZF4G01kr8eBd/5agkt9oCqz75tJMSIsquvRZ2sL6Hi5zGVKi/0OSC9oO1GHfJ2AV0ZIOY9vye0A==", + "engines": { + "node": ">=10.6.0" + } + }, "node_modules/call-bind": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", @@ -12479,6 +12498,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/packet-reader": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", + "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -12627,11 +12651,88 @@ "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", "optional": true }, + "node_modules/pg": { + "version": "8.7.3", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.7.3.tgz", + "integrity": "sha512-HPmH4GH4H3AOprDJOazoIcpI49XFsHCe8xlrjHkWiapdbHK+HLtbm/GQzXYAZwmPju/kzKhjaSfMACG+8cgJcw==", + "dependencies": { + "buffer-writer": "2.0.0", + "packet-reader": "1.0.0", + "pg-connection-string": "^2.5.0", + "pg-pool": "^3.5.1", + "pg-protocol": "^1.5.0", + "pg-types": "^2.1.0", + "pgpass": "1.x" + }, + "engines": { + "node": ">= 8.0.0" + }, + "peerDependencies": { + "pg-native": ">=2.0.0" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, "node_modules/pg-connection-string": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz", "integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==" }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-pool": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.5.1.tgz", + "integrity": "sha512-6iCR0wVrro6OOHFsyavV+i6KYL4lVNyYAB9RD18w66xSzN+d8b66HiwuP30Gp1SH5O9T82fckkzsRjlrhD0ioQ==", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.5.0.tgz", + "integrity": "sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ==" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "dependencies": { + "split2": "^4.1.0" + } + }, + "node_modules/pgpass/node_modules/split2": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.1.0.tgz", + "integrity": "sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ==", + "engines": { + "node": ">= 10.x" + } + }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -12887,6 +12988,41 @@ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "dev": true }, + "node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -14297,13 +14433,13 @@ } }, "node_modules/socks-proxy-agent": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz", - "integrity": "sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.1.1.tgz", + "integrity": "sha512-t8J0kG3csjA4g6FTbsMOWws+7R7vuRC8aQ/wy3/1OWmsgwA68zs/+cExQ0koSitUDXqhufF/YJr9wtNMZHw5Ew==", "dependencies": { "agent-base": "^6.0.2", - "debug": "^4.3.3", - "socks": "^2.6.2" + "debug": "^4.3.1", + "socks": "^2.6.1" }, "engines": { "node": ">= 10" @@ -20007,6 +20143,11 @@ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" }, + "buffer-writer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", + "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==" + }, "bulk-write-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/bulk-write-stream/-/bulk-write-stream-2.0.1.tgz", @@ -20022,6 +20163,11 @@ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==" }, + "cacheable-lookup": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-6.0.4.tgz", + "integrity": "sha512-mbcDEZCkv2CZF4G01kr8eBd/5agkt9oCqz75tJMSIsquvRZ2sL6Hi5zGVKi/0OSC9oO1GHfJ2AV0ZIOY9vye0A==" + }, "call-bind": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", @@ -25722,6 +25868,11 @@ "p-timeout": "^3.0.0" } }, + "packet-reader": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", + "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==" + }, "parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -25831,11 +25982,67 @@ "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", "optional": true }, + "pg": { + "version": "8.7.3", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.7.3.tgz", + "integrity": "sha512-HPmH4GH4H3AOprDJOazoIcpI49XFsHCe8xlrjHkWiapdbHK+HLtbm/GQzXYAZwmPju/kzKhjaSfMACG+8cgJcw==", + "requires": { + "buffer-writer": "2.0.0", + "packet-reader": "1.0.0", + "pg-connection-string": "^2.5.0", + "pg-pool": "^3.5.1", + "pg-protocol": "^1.5.0", + "pg-types": "^2.1.0", + "pgpass": "1.x" + } + }, "pg-connection-string": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz", "integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==" }, + "pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==" + }, + "pg-pool": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.5.1.tgz", + "integrity": "sha512-6iCR0wVrro6OOHFsyavV+i6KYL4lVNyYAB9RD18w66xSzN+d8b66HiwuP30Gp1SH5O9T82fckkzsRjlrhD0ioQ==" + }, + "pg-protocol": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.5.0.tgz", + "integrity": "sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ==" + }, + "pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "requires": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + } + }, + "pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "requires": { + "split2": "^4.1.0" + }, + "dependencies": { + "split2": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.1.0.tgz", + "integrity": "sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ==" + } + } + }, "picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -26004,6 +26211,29 @@ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "dev": true }, + "postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==" + }, + "postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==" + }, + "postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==" + }, + "postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "requires": { + "xtend": "^4.0.0" + } + }, "prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -27081,13 +27311,13 @@ } }, "socks-proxy-agent": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz", - "integrity": "sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.1.1.tgz", + "integrity": "sha512-t8J0kG3csjA4g6FTbsMOWws+7R7vuRC8aQ/wy3/1OWmsgwA68zs/+cExQ0koSitUDXqhufF/YJr9wtNMZHw5Ew==", "requires": { "agent-base": "^6.0.2", - "debug": "^4.3.3", - "socks": "^2.6.2" + "debug": "^4.3.1", + "socks": "^2.6.1" } }, "sortablejs": { diff --git a/package.json b/package.json index b15370f6..ea6c5a79 100644 --- a/package.json +++ b/package.json @@ -68,6 +68,7 @@ "badge-maker": "^3.3.1", "bcryptjs": "~2.4.3", "bree": "~7.1.5", + "cacheable-lookup": "~6.0.4", "chardet": "^1.3.0", "check-password-strength": "^2.0.5", "cheerio": "^1.0.0-rc.10", @@ -93,12 +94,14 @@ "nodemailer": "~6.6.5", "notp": "~2.0.3", "password-hash": "~1.2.2", + "pg": "^8.7.3", + "pg-connection-string": "^2.5.0", "prom-client": "~13.2.0", "prometheus-api-metrics": "~3.2.1", "redbean-node": "0.1.4", "socket.io": "~4.4.1", "socket.io-client": "~4.4.1", - "socks-proxy-agent": "^6.1.1", + "socks-proxy-agent": "6.1.1", "tar": "^6.1.11", "tcp-ping": "~0.1.1", "thirty-two": "~1.0.2" diff --git a/server/cacheable-dns-http-agent.js b/server/cacheable-dns-http-agent.js new file mode 100644 index 00000000..56e8430e --- /dev/null +++ b/server/cacheable-dns-http-agent.js @@ -0,0 +1,54 @@ +const https = require("https"); +const http = require("http"); +const CacheableLookup = require("cacheable-lookup"); + +class CacheableDnsHttpAgent { + + static cacheable = new CacheableLookup(); + + static httpAgentList = {}; + static httpsAgentList = {}; + + /** + * Register cacheable to global agents + */ + static registerGlobalAgent() { + this.cacheable.install(http.globalAgent); + this.cacheable.install(https.globalAgent); + } + + static install(agent) { + this.cacheable.install(agent); + } + + /** + * @var {https.AgentOptions} agentOptions + * @return {https.Agent} + */ + static getHttpsAgent(agentOptions) { + let key = JSON.stringify(agentOptions); + if (!(key in this.httpsAgentList)) { + this.httpsAgentList[key] = new https.Agent(agentOptions); + this.cacheable.install(this.httpsAgentList[key]); + } + return this.httpsAgentList[key]; + } + + /** + * @var {http.AgentOptions} agentOptions + * @return {https.Agents} + */ + static getHttpAgent(agentOptions) { + let key = JSON.stringify(agentOptions); + if (!(key in this.httpAgentList)) { + this.httpAgentList[key] = new http.Agent(agentOptions); + this.cacheable.install(this.httpAgentList[key]); + } + return this.httpAgentList[key]; + } + +} + +module.exports = { + CacheableDnsHttpAgent, +}; diff --git a/server/model/monitor.js b/server/model/monitor.js index b8733a0b..81149b52 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -7,7 +7,7 @@ dayjs.extend(timezone); const axios = require("axios"); const { Prometheus } = require("../prometheus"); const { log, UP, DOWN, PENDING, flipStatus, TimeLogger } = require("../../src/util"); -const { tcping, ping, dnsResolve, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, mssqlQuery, mqttAsync, setSetting, httpNtlm } = require("../util-server"); +const { tcping, ping, dnsResolve, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, mssqlQuery, postgresQuery, mqttAsync, setSetting, httpNtlm } = require("../util-server"); const { R } = require("redbean-node"); const { BeanModel } = require("redbean-node/dist/bean-model"); const { Notification } = require("../notification"); @@ -16,6 +16,7 @@ const { demoMode } = require("../config"); const version = require("../../package.json").version; const apicache = require("../modules/apicache"); const { UptimeKumaServer } = require("../uptime-kuma-server"); +const { CacheableDnsHttpAgent } = require("../cacheable-dns-http-agent"); /** * status: @@ -440,10 +441,13 @@ class Monitor extends BeanModel { "Accept": "*/*", "User-Agent": "Uptime-Kuma/" + version, }, - httpsAgent: new https.Agent({ + httpsAgent: CacheableDnsHttpAgent.getHttpsAgent({ maxCachedSessions: 0, // Use Custom agent to disable session reuse (https://github.com/nodejs/node/issues/3940) rejectUnauthorized: !this.getIgnoreTls(), }), + httpAgent: CacheableDnsHttpAgent.getHttpAgent({ + maxCachedSessions: 0, + }), maxRedirects: this.maxredirects, validateStatus: (status) => { return checkStatusCode(status, this.getAcceptedStatuscodes()); @@ -477,6 +481,14 @@ class Monitor extends BeanModel { await mssqlQuery(this.databaseConnectionString, this.databaseQuery); + bean.msg = ""; + bean.status = UP; + bean.ping = dayjs().valueOf() - startTime; + } else if (this.type === "postgres") { + let startTime = dayjs().valueOf(); + + await postgresQuery(this.databaseConnectionString, this.databaseQuery); + bean.msg = ""; bean.status = UP; bean.ping = dayjs().valueOf() - startTime; diff --git a/server/notification-providers/alertnow.js b/server/notification-providers/alertnow.js new file mode 100644 index 00000000..d778b01d --- /dev/null +++ b/server/notification-providers/alertnow.js @@ -0,0 +1,50 @@ +const NotificationProvider = require("./notification-provider"); +const axios = require("axios"); +const { setting } = require("../util-server"); +const { getMonitorRelativeURL, UP, DOWN } = require("../../src/util"); + +class AlertNow extends NotificationProvider { + + name = "AlertNow"; + + async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { + let okMsg = "Sent Successfully."; + try { + let textMsg = ""; + let status = "open"; + let eventType = "ERROR"; + let eventId = new Date().toISOString().slice(0, 10).replace(/-/g, ""); + + if (heartbeatJSON && heartbeatJSON.status === UP) { + textMsg = `[${heartbeatJSON.name}] ✅ Application is back online`; + status = "close"; + eventType = "INFO"; + eventId += `_${heartbeatJSON.name.replace(/\s/g, "")}`; + } else if (heartbeatJSON && heartbeatJSON.status === DOWN) { + textMsg = `[${heartbeatJSON.name}] 🔴 Application went down`; + } + + textMsg += ` - ${msg}`; + + const baseURL = await setting("primaryBaseURL"); + if (baseURL && monitorJSON) { + textMsg += ` >> ${baseURL + getMonitorRelativeURL(monitorJSON.id)}`; + } + + const data = { + "summary": textMsg, + "status": status, + "event_type": eventType, + "event_id": eventId, + }; + + await axios.post(notification.alertNowWebhookURL, data); + return okMsg; + } catch (error) { + this.throwGeneralAxiosError(error); + } + + } +} + +module.exports = AlertNow; diff --git a/server/notification-providers/linenotify.js b/server/notification-providers/linenotify.js new file mode 100644 index 00000000..8454152d --- /dev/null +++ b/server/notification-providers/linenotify.js @@ -0,0 +1,43 @@ +const NotificationProvider = require("./notification-provider"); +const axios = require("axios"); +const qs = require("qs"); +const { DOWN, UP } = require("../../src/util"); + +class LineNotify extends NotificationProvider { + + name = "LineNotify"; + + async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { + let okMsg = "Sent Successfully."; + try { + let lineAPIUrl = "https://notify-api.line.me/api/notify"; + let config = { + headers: { + "Content-Type": "application/x-www-form-urlencoded", + "Authorization": "Bearer " + notification.lineNotifyAccessToken + } + }; + if (heartbeatJSON == null) { + let testMessage = { + "message": msg, + }; + await axios.post(lineAPIUrl, qs.stringify(testMessage), config); + } else if (heartbeatJSON["status"] === DOWN) { + let downMessage = { + "message": "\n[🔴 Down]\n" + "Name: " + monitorJSON["name"] + " \n" + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"] + }; + await axios.post(lineAPIUrl, qs.stringify(downMessage), config); + } else if (heartbeatJSON["status"] === UP) { + let upMessage = { + "message": "\n[✅ Up]\n" + "Name: " + monitorJSON["name"] + " \n" + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"] + }; + await axios.post(lineAPIUrl, qs.stringify(upMessage), config); + } + return okMsg; + } catch (error) { + this.throwGeneralAxiosError(error); + } + } +} + +module.exports = LineNotify; diff --git a/server/notification.js b/server/notification.js index c457ed14..ad1c8705 100644 --- a/server/notification.js +++ b/server/notification.js @@ -1,40 +1,42 @@ const { R } = require("redbean-node"); +const { log } = require("../src/util"); +const Alerta = require("./notification-providers/alerta"); +const AlertNow = require("./notification-providers/alertnow"); +const AliyunSms = require("./notification-providers/aliyun-sms"); const Apprise = require("./notification-providers/apprise"); -const Discord = require("./notification-providers/discord"); -const Gotify = require("./notification-providers/gotify"); -const Ntfy = require("./notification-providers/ntfy"); -const Line = require("./notification-providers/line"); -const LunaSea = require("./notification-providers/lunasea"); -const Mattermost = require("./notification-providers/mattermost"); -const Matrix = require("./notification-providers/matrix"); -const Octopush = require("./notification-providers/octopush"); -const PromoSMS = require("./notification-providers/promosms"); +const Bark = require("./notification-providers/bark"); const ClickSendSMS = require("./notification-providers/clicksendsms"); +const DingDing = require("./notification-providers/dingding"); +const Discord = require("./notification-providers/discord"); +const Feishu = require("./notification-providers/feishu"); +const GoogleChat = require("./notification-providers/google-chat"); +const Gorush = require("./notification-providers/gorush"); +const Gotify = require("./notification-providers/gotify"); +const Line = require("./notification-providers/line"); +const LineNotify = require("./notification-providers/linenotify"); +const LunaSea = require("./notification-providers/lunasea"); +const Matrix = require("./notification-providers/matrix"); +const Mattermost = require("./notification-providers/mattermost"); +const Ntfy = require("./notification-providers/ntfy"); +const Octopush = require("./notification-providers/octopush"); +const OneBot = require("./notification-providers/onebot"); +const PagerDuty = require("./notification-providers/pagerduty"); +const PromoSMS = require("./notification-providers/promosms"); const Pushbullet = require("./notification-providers/pushbullet"); +const PushDeer = require("./notification-providers/pushdeer"); const Pushover = require("./notification-providers/pushover"); const Pushy = require("./notification-providers/pushy"); -const TechulusPush = require("./notification-providers/techulus-push"); const RocketChat = require("./notification-providers/rocket-chat"); +const SerwerSMS = require("./notification-providers/serwersms"); const Signal = require("./notification-providers/signal"); const Slack = require("./notification-providers/slack"); const SMTP = require("./notification-providers/smtp"); +const Stackfield = require("./notification-providers/stackfield"); const Teams = require("./notification-providers/teams"); +const TechulusPush = require("./notification-providers/techulus-push"); const Telegram = require("./notification-providers/telegram"); const Webhook = require("./notification-providers/webhook"); -const Feishu = require("./notification-providers/feishu"); -const AliyunSms = require("./notification-providers/aliyun-sms"); -const DingDing = require("./notification-providers/dingding"); -const Bark = require("./notification-providers/bark"); -const { log } = require("../src/util"); -const SerwerSMS = require("./notification-providers/serwersms"); -const Stackfield = require("./notification-providers/stackfield"); const WeCom = require("./notification-providers/wecom"); -const GoogleChat = require("./notification-providers/google-chat"); -const PagerDuty = require("./notification-providers/pagerduty"); -const Gorush = require("./notification-providers/gorush"); -const Alerta = require("./notification-providers/alerta"); -const OneBot = require("./notification-providers/onebot"); -const PushDeer = require("./notification-providers/pushdeer"); class Notification { @@ -47,41 +49,43 @@ class Notification { this.providerList = {}; const list = [ - new Apprise(), + new Alerta(), + new AlertNow(), new AliyunSms(), + new Apprise(), + new Bark(), + new ClickSendSMS(), new DingDing(), new Discord(), - new Teams(), - new Gotify(), - new Ntfy(), - new Line(), - new LunaSea(), new Feishu(), - new Mattermost(), + new GoogleChat(), + new Gorush(), + new Gotify(), + new Line(), + new LineNotify(), + new LunaSea(), new Matrix(), + new Mattermost(), + new Ntfy(), new Octopush(), + new OneBot(), + new PagerDuty(), new PromoSMS(), - new ClickSendSMS(), new Pushbullet(), + new PushDeer(), new Pushover(), new Pushy(), - new TechulusPush(), new RocketChat(), + new SerwerSMS(), new Signal(), new Slack(), new SMTP(), + new Stackfield(), + new Teams(), + new TechulusPush(), new Telegram(), new Webhook(), - new Bark(), - new SerwerSMS(), - new Stackfield(), new WeCom(), - new GoogleChat(), - new PagerDuty(), - new Gorush(), - new Alerta(), - new OneBot(), - new PushDeer(), ]; for (let item of list) { diff --git a/server/socket-handlers/status-page-socket-handler.js b/server/socket-handlers/status-page-socket-handler.js index 80017e7d..16d6ee73 100644 --- a/server/socket-handlers/status-page-socket-handler.js +++ b/server/socket-handlers/status-page-socket-handler.js @@ -202,7 +202,11 @@ module.exports.statusPageSocketHandler = (socket) => { relationBean.weight = monitorOrder++; relationBean.group_id = groupBean.id; relationBean.monitor_id = monitor.id; - relationBean.send_url = monitor.sendUrl; + + if (monitor.sendUrl !== undefined) { + relationBean.send_url = monitor.sendUrl; + } + await R.store(relationBean); } diff --git a/server/uptime-kuma-server.js b/server/uptime-kuma-server.js index 34031b23..3362f72c 100644 --- a/server/uptime-kuma-server.js +++ b/server/uptime-kuma-server.js @@ -7,6 +7,7 @@ const { R } = require("redbean-node"); const { log } = require("../src/util"); const Database = require("./database"); const util = require("util"); +const { CacheableDnsHttpAgent } = require("./cacheable-dns-http-agent"); /** * `module.exports` (alias: `server`) should be inside this class, in order to avoid circular dependency issue. @@ -70,6 +71,8 @@ class UptimeKumaServer { } } + CacheableDnsHttpAgent.registerGlobalAgent(); + this.io = new Server(this.httpServer); } diff --git a/server/util-server.js b/server/util-server.js index f6a0e396..84244b02 100644 --- a/server/util-server.js +++ b/server/util-server.js @@ -11,6 +11,8 @@ const mqtt = require("mqtt"); const chroma = require("chroma-js"); const { badgeConstants } = require("./config"); const mssql = require("mssql"); +const { Client } = require("pg"); +const postgresConParse = require("pg-connection-string").parse; const { NtlmClient } = require("axios-ntlm"); const { Settings } = require("./settings"); @@ -238,10 +240,6 @@ exports.dnsResolve = function (hostname, resolverServer, resolverPort, rrtype) { */ exports.mssqlQuery = function (connectionString, query) { return new Promise((resolve, reject) => { - mssql.on("error", err => { - reject(err); - }); - mssql.connect(connectionString).then(pool => { return pool.request() .query(query); @@ -255,6 +253,38 @@ exports.mssqlQuery = function (connectionString, query) { }); }; +/** + * Run a query on Postgres + * @param {string} connectionString The database connection string + * @param {string} query The query to validate the database with + * @returns {Promise<(string[]|Object[]|Object)>} + */ +exports.postgresQuery = function (connectionString, query) { + return new Promise((resolve, reject) => { + const config = postgresConParse(connectionString); + + if (config.password === "") { + // See https://github.com/brianc/node-postgres/issues/1927 + return reject(new Error("Password is undefined.")); + } + + const client = new Client({ connectionString }); + + client.connect(); + + return client.query(query) + .then(res => { + resolve(res); + }) + .catch(err => { + reject(err); + }) + .finally(() => { + client.end(); + }); + }); +}; + /** * Retrieve value of setting based on key * @param {string} key Key of setting to retrieve @@ -384,7 +414,7 @@ exports.checkCertificate = function (res) { /** * Check if the provided status code is within the accepted ranges - * @param {string} status The status code to check + * @param {number} status The status code to check * @param {string[]} acceptedCodes An array of accepted status codes * @returns {boolean} True if status code within range, false otherwise * @throws {Error} Will throw an error if the provided status code is not a valid range string or code string diff --git a/src/components/notifications/AlertNow.vue b/src/components/notifications/AlertNow.vue new file mode 100644 index 00000000..93acc959 --- /dev/null +++ b/src/components/notifications/AlertNow.vue @@ -0,0 +1,13 @@ + diff --git a/src/components/notifications/LineNotify.vue b/src/components/notifications/LineNotify.vue new file mode 100644 index 00000000..0f6897f4 --- /dev/null +++ b/src/components/notifications/LineNotify.vue @@ -0,0 +1,9 @@ + diff --git a/src/components/notifications/index.js b/src/components/notifications/index.js index 18c316a5..c1b7da4a 100644 --- a/src/components/notifications/index.js +++ b/src/components/notifications/index.js @@ -1,38 +1,40 @@ -import STMP from "./SMTP.vue"; -import Telegram from "./Telegram.vue"; +import Alerta from "./Alerta.vue"; +import AlertNow from "./AlertNow.vue"; +import AliyunSMS from "./AliyunSms.vue"; +import Apprise from "./Apprise.vue"; +import Bark from "./Bark.vue"; +import ClickSendSMS from "./ClickSendSMS.vue"; +import DingDing from "./DingDing.vue"; import Discord from "./Discord.vue"; -import Webhook from "./Webhook.vue"; -import Signal from "./Signal.vue"; +import Feishu from "./Feishu.vue"; +import GoogleChat from "./GoogleChat.vue"; +import Gorush from "./Gorush.vue"; import Gotify from "./Gotify.vue"; +import Line from "./Line.vue"; +import LineNotify from "./LineNotify.vue"; +import LunaSea from "./LunaSea.vue"; +import Matrix from "./Matrix.vue"; +import Mattermost from "./Mattermost.vue"; import Ntfy from "./Ntfy.vue"; -import Slack from "./Slack.vue"; -import RocketChat from "./RocketChat.vue"; -import Teams from "./Teams.vue"; +import Octopush from "./Octopush.vue"; +import OneBot from "./OneBot.vue"; +import PagerDuty from "./PagerDuty.vue"; +import PromoSMS from "./PromoSMS.vue"; +import Pushbullet from "./Pushbullet.vue"; +import PushDeer from "./PushDeer.vue"; import Pushover from "./Pushover.vue"; import Pushy from "./Pushy.vue"; -import TechulusPush from "./TechulusPush.vue"; -import Octopush from "./Octopush.vue"; -import PromoSMS from "./PromoSMS.vue"; -import ClickSendSMS from "./ClickSendSMS.vue"; -import LunaSea from "./LunaSea.vue"; -import Feishu from "./Feishu.vue"; -import Apprise from "./Apprise.vue"; -import Pushbullet from "./Pushbullet.vue"; -import Line from "./Line.vue"; -import Mattermost from "./Mattermost.vue"; -import Matrix from "./Matrix.vue"; -import AliyunSMS from "./AliyunSms.vue"; -import DingDing from "./DingDing.vue"; -import Bark from "./Bark.vue"; +import RocketChat from "./RocketChat.vue"; import SerwerSMS from "./SerwerSMS.vue"; +import Signal from "./Signal.vue"; +import Slack from "./Slack.vue"; import Stackfield from "./Stackfield.vue"; +import STMP from "./SMTP.vue"; +import Teams from "./Teams.vue"; +import TechulusPush from "./TechulusPush.vue"; +import Telegram from "./Telegram.vue"; +import Webhook from "./Webhook.vue"; import WeCom from "./WeCom.vue"; -import GoogleChat from "./GoogleChat.vue"; -import PagerDuty from "./PagerDuty.vue"; -import Gorush from "./Gorush.vue"; -import Alerta from "./Alerta.vue"; -import OneBot from "./OneBot.vue"; -import PushDeer from "./PushDeer.vue"; /** * Manage all notification form. @@ -40,41 +42,43 @@ import PushDeer from "./PushDeer.vue"; * @type { Record } */ const NotificationFormList = { - "telegram": Telegram, - "webhook": Webhook, - "smtp": STMP, - "discord": Discord, - "teams": Teams, - "signal": Signal, - "gotify": Gotify, - "ntfy": Ntfy, - "slack": Slack, - "rocket.chat": RocketChat, - "pushover": Pushover, - "pushy": Pushy, - "PushByTechulus": TechulusPush, - "octopush": Octopush, - "promosms": PromoSMS, - "clicksendsms": ClickSendSMS, - "lunasea": LunaSea, - "Feishu": Feishu, + "alerta": Alerta, + "AlertNow": AlertNow, "AliyunSMS": AliyunSMS, "apprise": Apprise, - "pushbullet": Pushbullet, - "line": Line, - "mattermost": Mattermost, - "matrix": Matrix, - "DingDing": DingDing, "Bark": Bark, - "serwersms": SerwerSMS, - "stackfield": Stackfield, - "WeCom": WeCom, + "clicksendsms": ClickSendSMS, + "DingDing": DingDing, + "discord": Discord, + "Feishu": Feishu, "GoogleChat": GoogleChat, - "PagerDuty": PagerDuty, "gorush": Gorush, - "alerta": Alerta, + "gotify": Gotify, + "line": Line, + "LineNotify": LineNotify, + "lunasea": LunaSea, + "matrix": Matrix, + "mattermost": Mattermost, + "ntfy": Ntfy, + "octopush": Octopush, "OneBot": OneBot, + "PagerDuty": PagerDuty, + "promosms": PromoSMS, + "pushbullet": Pushbullet, + "PushByTechulus": TechulusPush, "PushDeer": PushDeer, + "pushover": Pushover, + "pushy": Pushy, + "rocket.chat": RocketChat, + "serwersms": SerwerSMS, + "signal": Signal, + "slack": Slack, + "smtp": STMP, + "stackfield": Stackfield, + "teams": Teams, + "telegram": Telegram, + "webhook": Webhook, + "WeCom": WeCom, }; export default NotificationFormList; diff --git a/src/i18n.js b/src/i18n.js index d848cf3d..8495cd99 100644 --- a/src/i18n.js +++ b/src/i18n.js @@ -11,6 +11,7 @@ const languageList = { "es-ES": "Español", "eu": "Euskara", "fa": "Farsi", + "pt-PT": "Português (Portugal)", "pt-BR": "Português (Brasileiro)", "fr-FR": "Français (France)", "hu": "Magyar", diff --git a/src/languages/bg-BG.js b/src/languages/bg-BG.js index b2c185d9..56035295 100644 --- a/src/languages/bg-BG.js +++ b/src/languages/bg-BG.js @@ -536,4 +536,5 @@ export default { Domain: "Домейн", Workstation: "Работна станция", disableCloudflaredNoAuthMsg: "Тъй като сте в режим \"No Auth mode\", парола не се изисква.", + wayToGetLineNotifyToken: "Може да получите токен код за достъп от {0}", }; diff --git a/src/languages/en.js b/src/languages/en.js index 9f20cd5d..352a63f6 100644 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -539,4 +539,5 @@ export default { "Workstation": "Workstation", disableCloudflaredNoAuthMsg: "You are in No Auth mode, password is not require.", trustProxyDescription: "Trust 'X-Forwarded-*' headers. If you want to get the correct client IP and your Uptime Kuma is behind such as Nginx or Apache, you should enable this.", + wayToGetLineNotifyToken: "You can get an access token from {0}", }; diff --git a/src/languages/es-ES.js b/src/languages/es-ES.js index 31538295..51327740 100644 --- a/src/languages/es-ES.js +++ b/src/languages/es-ES.js @@ -7,8 +7,8 @@ export default { maxRedirectDescription: "Número máximo de direcciones a seguir. Establecer a 0 para deshabilitar.", acceptedStatusCodesDescription: "Seleccionar los códigos de estado que se consideran como respuesta exitosa.", passwordNotMatchMsg: "La contraseña repetida no coincide.", - notificationDescription: "Por favor asigne una notificación a el/los monitor(es) para hacerlos funcional(es).", - keywordDescription: "Palabra clave en HTML plano o respuesta JSON y es sensible a mayúsculas", + notificationDescription: "Por favor asigna una notificación a el/los monitor(es) para hacerlos funcional(es).", + keywordDescription: "Palabra clave en HTML plano o respuesta JSON, es sensible a mayúsculas", pauseDashboardHome: "Pausado", deleteMonitorMsg: "¿Seguro que quieres eliminar este monitor?", deleteNotificationMsg: "¿Seguro que quieres eliminar esta notificación para todos los monitores?", @@ -35,7 +35,7 @@ export default { Pause: "Pausar", Name: "Nombre", Status: "Estado", - DateTime: "Fecha y Hora", + DateTime: "Fecha y hora", Message: "Mensaje", "No important events": "No hay eventos importantes", Resume: "Reanudar", @@ -50,7 +50,7 @@ export default { "-hour": "-hora", Response: "Respuesta", Ping: "Ping", - "Monitor Type": "Tipo de Monitor", + "Monitor Type": "Tipo de monitor", Keyword: "Palabra clave", "Friendly Name": "Nombre sencillo", URL: "URL", @@ -60,11 +60,11 @@ export default { Retries: "Reintentos", Advanced: "Avanzado", "Upside Down Mode": "Modo invertido", - "Max. Redirects": "Redirecciones Máximas", + "Max. Redirects": "Redirecciones máximas", "Accepted Status Codes": "Códigos de estado aceptados", Save: "Guardar", Notifications: "Notificaciones", - "Not available, please setup.": "No disponible, por favor configúrelo.", + "Not available, please setup.": "No disponible, por favor configúralo.", "Setup Notification": "Configurar notificación", Light: "Claro", Dark: "Oscuro", @@ -82,8 +82,8 @@ export default { "New Password": "Nueva contraseña", "Repeat New Password": "Repetir nueva contraseña", "Update Password": "Actualizar contraseña", - "Disable Auth": "Deshabilitar Autenticación", - "Enable Auth": "Habilitar Autenticación", + "Disable Auth": "Deshabilitar autenticación", + "Enable Auth": "Habilitar autenticación", "disableauth.message1": "Seguro que deseas deshabilitar la autenticación?", "disableauth.message2": "Es para quien implementa autenticación de terceros ante Uptime Kuma como por ejemplo Cloudflare Access.", "Please use this option carefully!": "Por favor usar con cuidado.", @@ -104,32 +104,32 @@ export default { Test: "Test", "Certificate Info": "Información del certificado", "Resolver Server": "Servidor de resolución", - "Resource Record Type": "Tipo de Registro", + "Resource Record Type": "Tipo de registro", "Last Result": "Último resultado", "Create your admin account": "Crea tu cuenta de administrador", "Repeat Password": "Repetir contraseña", respTime: "Tiempo de resp. (ms)", notAvailableShort: "N/A", Create: "Crear", - clearEventsMsg: "¿Está seguro de que desea eliminar todos los eventos de este monitor?", - clearHeartbeatsMsg: "¿Está seguro de que desea eliminar todos los latidos de este monitor?", - confirmClearStatisticsMsg: "¿Está seguro de que desea eliminar TODAS las estadísticas?", - "Clear Data": "Borrar Datos", + clearEventsMsg: "¿Estás seguro de que deseas eliminar todos los eventos de este monitor?", + clearHeartbeatsMsg: "¿Estás seguro de que deseas eliminar todos los latidos de este monitor?", + confirmClearStatisticsMsg: "¿Estás seguro de que deseas eliminar TODAS las estadísticas?", + "Clear Data": "Borrar datos", Events: "Eventos", Heartbeats: "Latidos", "Auto Get": "Obtener automáticamente", - enableDefaultNotificationDescription: "Para cada nuevo monitor, esta notificación estará habilitada de forma predeterminada. Aún puede deshabilitar la notificación por separado para cada monitor.", + enableDefaultNotificationDescription: "Para cada nuevo monitor, esta notificación estará habilitada de forma predeterminada. Aún puedes deshabilitar la notificación por separado para cada monitor.", "Default enabled": "Habilitado por defecto", "Also apply to existing monitors": "También se aplica a monitores existentes", Export: "Exportar", Import: "Importar", - backupDescription: "Puede hacer una copia de seguridad de todos los monitores y todas las notificaciones en un archivo JSON.", + backupDescription: "Puedes hacer una copia de seguridad de todos los monitores y todas las notificaciones en un archivo JSON.", backupDescription2: "PD: el historial y los datos de eventos no están incluidos.", - backupDescription3: "Los datos confidenciales, como los tokens de notificación, se incluyen en el archivo de exportación. Guárdelo con cuidado.", - alertNoFile: "Seleccione un archivo para importar.", - alertWrongFileType: "Seleccione un archivo JSON.", - twoFAVerifyLabel: "Ingrese su token para verificar que 2FA está funcionando", - tokenValidSettingsMsg: "¡El token es válido! Ahora puede guardar la configuración de 2FA.", + backupDescription3: "Los datos confidenciales, como los tokens de notificación, se incluyen en el archivo de exportación. Guárdalo con cuidado.", + alertNoFile: "Selecciona un archivo para importar.", + alertWrongFileType: "Selecciona un archivo JSON.", + twoFAVerifyLabel: "Ingresa tu token para verificar que 2FA está funcionando", + tokenValidSettingsMsg: "¡El token es válido! Ahora puedes guardar la configuración de 2FA.", confirmEnableTwoFAMsg: "¿Estás seguro de que quieres habilitar 2FA?", confirmDisableTwoFAMsg: "¿Estás seguro de que quieres desactivar 2FA?", "Apply on all existing monitors": "Aplicar en todos los monitores existentes", @@ -145,19 +145,19 @@ export default { "Show URI": "Mostrar URI", "Clear all statistics": "Borrar todas las estadísticas", retryCheckEverySecond: "Reintentar cada {0} segundo.", - importHandleDescription: "Elija 'Omitir existente' si desea omitir todos los monitores o notificaciones con el mismo nombre. 'Sobrescribir' eliminará todos los monitores y notificaciones existentes.", - confirmImportMsg: "¿Estás seguro de importar la copia de seguridad? Asegúrese de haber seleccionado la opción de importación correcta.", + importHandleDescription: "Elige 'Omitir existente' si deseas omitir todos los monitores o notificaciones con el mismo nombre. 'Sobrescribir' eliminará todos los monitores y notificaciones existentes.", + confirmImportMsg: "¿Estás seguro de importar la copia de seguridad? Asegúrate de haber seleccionado la opción de importación correcta.", "Heartbeat Retry Interval": "Intervalo de reintento de latido", "Import Backup": "Importar copia de seguridad", "Export Backup": "Exportar copia de seguridad", "Skip existing": "Omitir existente", Overwrite: "Sobrescribir", Options: "Opciones", - "Keep both": "Mantén ambos", + "Keep both": "Manténer ambos", Tags: "Etiquetas", - "Add New below or Select...": "Agregar nuevo a continuación o Seleccionar...", - "Tag with this name already exist.": "La etiqueta con este nombre ya existe.", - "Tag with this value already exist.": "La etiqueta con este valor ya existe.", + "Add New below or Select...": "Agregar nuevo a continuación o seleccionar...", + "Tag with this name already exist.": "Una etiqueta con este nombre ya existe.", + "Tag with this value already exist.": "Una etiqueta con este valor ya existe.", color: "color", "value (optional)": "valor (opcional)", Gray: "Gris", @@ -172,17 +172,17 @@ export default { "Avg. Ping": "Ping promedio", "Avg. Response": "Respuesta promedio", "Entry Page": "Página de entrada", - statusPageNothing: "No hay nada aquí, agregue un grupo o un monitor.", + statusPageNothing: "No hay nada aquí, agrega un grupo o un monitor.", "No Services": "Sin servicio", "All Systems Operational": "Todos los sistemas están operativos", "Partially Degraded Service": "Servicio parcialmente degradado", "Degraded Service": "Servicio degradado", - "Add Group": "Agregar Grupo", + "Add Group": "Agregar grupo", "Add a monitor": "Agregar un monitor", "Edit Status Page": "Editar página de estado", "Go to Dashboard": "Ir al panel de control", "Status Page": "Página de estado", - "Status Pages": "Página de estado", + "Status Pages": "Páginas de estado", telegram: "Telegram", webhook: "Webhook", smtp: "Email (SMTP)", @@ -205,5 +205,5 @@ export default { clearDataOlderThan: "Mantener los datos del historial del monitor durante {0} días.", records: "registros", "One record": "Un registro", - steamApiKeyDescription: "Para monitorear un servidor de juegos de Steam, necesita una clave Steam Web-API. Puede registrar su clave API aquí: ", + steamApiKeyDescription: "Para monitorear un servidor de juegos de Steam, necesitas una clave Steam Web-API. Puedes registrar tu clave API aquí: ", }; diff --git a/src/languages/ko-KR.js b/src/languages/ko-KR.js index dbb02e65..f181389b 100644 --- a/src/languages/ko-KR.js +++ b/src/languages/ko-KR.js @@ -3,7 +3,7 @@ export default { checkEverySecond: "{0}초마다 확인해요.", retryCheckEverySecond: "{0}초마다 다시 확인해요.", retriesDescription: "서비스가 중단된 후 알림을 보내기 전 최대 재시도 횟수", - ignoreTLSError: "HTTPS 웹사이트에서 TLS/SSL 에러 무시하기", + ignoreTLSError: "HTTPS 웹사이트에서 TLS/SSL 오류 무시하기", upsideDownModeDescription: "서버 상태를 반대로 표시해요. 서버가 작동하면 오프라인으로 표시할 거예요.", maxRedirectDescription: "최대 리다이렉트 횟수예요. 0을 입력하면 리다이렉트를 꺼요.", acceptedStatusCodesDescription: "응답 성공으로 간주할 상태 코드를 정해요.", @@ -30,7 +30,7 @@ export default { Dashboard: "대시보드", "New Update": "새로운 업데이트", Language: "언어", - Appearance: "외형", + Appearance: "디스플레이", Theme: "테마", General: "일반", Version: "버전", @@ -78,7 +78,7 @@ export default { Notifications: "알림", "Not available, please setup.": "존재하지 않아요, 새로운 거 하나 만드는 건 어때요?", "Setup Notification": "알림 설정", - Light: "라이트", + Light: "화이트", Dark: "다크", Auto: "자동", "Theme - Heartbeat Bar": "테마 - 하트비트 바", @@ -91,7 +91,7 @@ export default { "Discourage search engines from indexing site": "검색 엔진 인덱싱 거부", "Change Password": "비밀번호 변경", "Current Password": "기존 비밀번호", - "New Password": "새로운 비밀번호", + "New Password": "새 비밀번호", "Repeat New Password": "새로운 비밀번호 재입력", "Update Password": "비밀번호 변경", "Disable Auth": "인증 비활성화", @@ -109,14 +109,14 @@ export default { Password: "비밀번호", "Remember me": "비밀번호 기억하기", Login: "로그인", - "No Monitors, please": "모니터링이 없어요,", - "add one": "하나 추가해봐요", + "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": "비밀번호 재입력", @@ -208,19 +208,19 @@ export default { smtpBCC: "숨은 참조", discord: "Discord", "Discord Webhook URL": "Discord Webhook URL", - wayToGetDiscordURL: "서버 설정 -> 연동 -> 웹후크 보기 -> 새 웹후크에서 얻을 수 있어요.", + wayToGetDiscordURL: "서버 설정 -> 연동 -> 웹후크 보기 -> 새 웹후크에서 얻을 수 있어요!", "Bot Display Name": "표시 이름", "Prefix Custom Message": "접두사 메시지", "Hello @everyone is...": "{'@'}everyone 서버 상태 알림이에요...", teams: "Microsoft Teams", "Webhook URL": "Webhook URL", - wayToGetTeamsURL: "{0}에서 Webhook을 어떻게 만드는지 알아봐요.", + wayToGetTeamsURL: "{0}에서 Webhook을 어떻게 만드는지 알아보세요!", signal: "Signal", Number: "숫자", Recipients: "받는 사람", needSignalAPI: "REST API를 사용하는 Signal 클라이언트가 있어야 해요.", wayToCheckSignalURL: "밑에 URL을 확인해 URL 설정 방법을 볼 수 있어요.", - signalImportant: "중요: 받는 사람의 그룹과 숫자는 섞을 수 없어요!", + signalImportant: "경고: 받는 사람의 그룹과 숫자는 섞을 수 없어요!", gotify: "Gotify", "Application Token": "애플리케이션 토큰", "Server URL": "서버 URL", @@ -230,8 +230,8 @@ export default { "Channel Name": "채널 이름", "Uptime Kuma URL": "Uptime Kuma URL", aboutWebhooks: "Webhook에 대한 설명: {0}", - aboutChannelName: "Webhook 채널을 우회하려면 {0} 채널 이름칸에 채널 이름을 입력해주세요. 예: #기타-채널", - aboutKumaURL: "Uptime Kuma URL칸을 공백으로 두면 기본적으로 Project Github 페이지로 설정해요.", + aboutChannelName: "Webhook 채널을 무시하려면 {0} 채널 이름칸에 채널 이름을 입력해주세요. 예: #기타-채널", + aboutKumaURL: "Uptime Kuma URL칸을 공백으로 두면 기본적으로 Github Project 페이지로 설정해요.", emojiCheatSheet: "이모지 목록 시트: {0}", "rocket.chat": "Rocket.chat", pushover: "Pushover", @@ -243,8 +243,8 @@ export default { pushbullet: "Pushbullet", line: "Line Messenger", mattermost: "Mattermost", - "User Key": "사용자 키", - Device: "장치", + "User Key": "유저 키", + Device: "디바이스", "Message Title": "메시지 제목", "Notification Sound": "알림음", "More info on:": "자세한 정보: {0}", @@ -254,7 +254,7 @@ export default { octopushTypePremium: "프리미엄 (빠름) - 알림 기능에 적합해요)", octopushTypeLowCost: "저렴한 요금 (느림) - 가끔 차단될 수 있어요)", "Check octopush prices": "{0}에서 Octopush 가격을 확인할 수 있어요.", - octopushPhoneNumber: "휴대전화 번호 (intl format, eg : +33612345678) ", + octopushPhoneNumber: "휴대전화 번호 (intl format, 예시: +821023456789) ", octopushSMSSender: "보내는 사람 이름 : 3-11개의 영숫자 및 여백공간 (a-z, A-Z, 0-9)", "LunaSea Device ID": "LunaSea 장치 ID", "Apprise URL": "Apprise URL", @@ -324,17 +324,17 @@ export default { Content: "내용", Style: "스타일", info: "정보", - warning: "경고", - danger: "위험", + warning: "주의", + danger: "경고", primary: "기본", - light: "라이트", + light: "화이트", dark: "다크", - Post: "올리기", + Post: "게시", "Please input title and content": "제목과 내용을 작성해주세요.", Created: "생성 날짜", "Last Updated": "마지막 업데이트", Unpin: "제거", - "Switch to Light Theme": "라이트 테마로 전환", + "Switch to Light Theme": "화이트 테마로 전환", "Switch to Dark Theme": "다크 테마로 전환", "Show Tags": "태그 보이기", "Hide Tags": "태그 숨기기", @@ -361,8 +361,8 @@ export default { topicExplanation: "모니터링할 MQTT Topic", successMessage: "성공 메시지", successMessageExplanation: "성공으로 간주되는 MQTT 메시지", - error: "error", - critical: "critical", + error: "오류", + critical: "크리티컬", Customize: "커스터마이즈", "Custom Footer": "커스텀 Footer", "Custom CSS": "커스텀 CSS", @@ -406,7 +406,7 @@ export default { PhoneNumbers: "휴대전화 번호", TemplateCode: "템플릿 코드", SignName: "SignName", - "Sms template must contain parameters: ": "Sms 템플릿은 다음과 같은 파라미터가 포함되어야 해요:", + "Sms template must contain parameters: ": "SMS 템플릿은 다음과 같은 파라미터가 포함되어야 해요:", "Bark Endpoint": "Bark Endpoint", WebHookUrl: "웹훅 URL", SecretKey: "Secret Key", @@ -518,14 +518,14 @@ export default { "Show update if available": "사용 가능한 경우에 업데이트 표시", "Also check beta release": "베타 릴리즈 확인", "Using a Reverse Proxy?": "리버스 프록시를 사용하시나요?", - "Check how to config it for WebSocket": "웹소켓에 대한 설정 방법 확인", + "Check how to config it for WebSocket": "웹소켓 대한 설정 방법", "Steam Game Server": "스팀 게임 서버", "Most likely causes:": "원인:", - "The resource is no longer available.": "더이상 사용할 수 없어요.", + "The resource is no longer available.": "더 이상 사용할 수 없어요...", "There might be a typing error in the address.": "주소에 오탈자가 있을 수 있어요.", "What you can try:": "해결 방법:", "Retype the address.": "주소 다시 입력하기", "Go back to the previous page.": "이전 페이지로 돌아가기", - "Coming Soon": "Coming Soon", + "Coming Soon": "Coming Soon...", wayToGetClickSendSMSToken: "{0}에서 API 사용자 이름과 키를 얻을 수 있어요.", }; diff --git a/src/languages/pt-PT.js b/src/languages/pt-PT.js new file mode 100644 index 00000000..21e68d26 --- /dev/null +++ b/src/languages/pt-PT.js @@ -0,0 +1,203 @@ +export default { + languageName: "Português (Portugal)", + checkEverySecond: "Verificar a cada {0} segundos.", + retryCheckEverySecond: "Tentar novamente a cada {0} segundos.", + retriesDescription: "Máximo de tentativas antes que o serviço seja marcado como inativo e uma notificação seja enviada", + ignoreTLSError: "Ignorar erros TLS/SSL para sites HTTPS", + upsideDownModeDescription: "Inverte o status de cabeça para baixo. Se o serviço estiver acessível, ele está OFFLINE.", + maxRedirectDescription: "Número máximo de redirecionamentos a seguir. Define como 0 para desativar redirecionamentos.", + acceptedStatusCodesDescription: "Seleciona os códigos de status que são considerados uma resposta bem-sucedida.", + passwordNotMatchMsg: "A senha repetida não corresponde.", + notificationDescription: "Atribuir uma notificação ao (s) monitor (es) para que funcione.", + keywordDescription: "Pesquisa a palavra-chave em HTML simples ou resposta JSON e diferencia maiúsculas de minúsculas", + pauseDashboardHome: "Pausa", + deleteMonitorMsg: "Tens a certeza de que queres excluir este monitor?", + deleteNotificationMsg: "Tens a certeza de que queres excluir esta notificação para todos os monitores?", + resolverserverDescription: "A Cloudflare é o servidor padrão, podes alterar o servidor 'resolvedor' a qualquer momento.", + rrtypeDescription: "Seleciona o RR-Type que queres monitorizar", + pauseMonitorMsg: "Tens a certeza que queres fazer uma pausa?", + enableDefaultNotificationDescription: "Para cada monitor novo esta notificação vai estar activa por padrão. Podes também desativar a notificação separadamente para cada monitor.", + clearEventsMsg: "Tens a certeza que queres excluir todos os eventos deste monitor?", + clearHeartbeatsMsg: "Tens a certeza de que queres excluir todos os heartbeats deste monitor?", + confirmClearStatisticsMsg: "Tens a certeza que queres excluir TODAS as estatísticas?", + importHandleDescription: "Escolhe 'Ignorar existente' se quiseres ignorar todos os monitores ou notificações com o mesmo nome. 'Substituir' excluirá todos os monitores e notificações existentes.", + confirmImportMsg: "Tens a certeza que queres importar o backup? Certifica-te que selecionaste a opção de importação correta.", + twoFAVerifyLabel: "Insire o teu token para verificares se o 2FA está a funcionar", + tokenValidSettingsMsg: "O token é válido! Agora podes salvar as configurações do 2FA.", + confirmEnableTwoFAMsg: "Tens a certeza de que queres habilitar 2FA?", + confirmDisableTwoFAMsg: "Tens a certeza de que queres desativar 2FA?", + Settings: "Configurações", + Dashboard: "Dashboard", + "New Update": "Nova Atualização", + Language: "Linguagem", + Appearance: "Aparência", + Theme: "Tema", + General: "Geral", + Version: "Versão", + "Check Update On GitHub": "Verificar atualização no Github", + List: "Lista", + Add: "Adicionar", + "Add New Monitor": "Adicionar novo monitor", + "Quick Stats": "Estatísticas rápidas", + Up: "On", + Down: "Off", + Pending: "Pendente", + Unknown: "Desconhecido", + Pause: "Pausa", + Name: "Nome", + Status: "Status", + DateTime: "Data hora", + Message: "Mensagem", + "No important events": "Nenhum evento importante", + Resume: "Resumo", + Edit: "Editar", + Delete: "Apagar", + Current: "Atual", + Uptime: "Tempo de atividade", + "Cert Exp.": "Cert Exp.", + day: "dia | dias", + "-day": "-dia", + hour: "hora", + "-hour": "-hora", + Response: "Resposta", + Ping: "Ping", + "Monitor Type": "Tipo de Monitor", + Keyword: "Palavra-Chave", + "Friendly Name": "Nome Amigável", + URL: "URL", + Hostname: "Hostname", + Port: "Porta", + "Heartbeat Interval": "Intervalo de Heartbeats", + Retries: "Novas tentativas", + "Heartbeat Retry Interval": "Intervalo de repetição de Heartbeats", + Advanced: "Avançado", + "Upside Down Mode": "Modo de cabeça para baixo", + "Max. Redirects": "Redirecionamento Máx.", + "Accepted Status Codes": "Status Code Aceitáveis", + Save: "Guardar", + Notifications: "Notificações", + "Not available, please setup.": "Não disponível, por favor configura.", + "Setup Notification": "Configurar Notificação", + Light: "Claro", + Dark: "Escuro", + Auto: "Auto", + "Theme - Heartbeat Bar": "Tema - Barra de Heartbeat", + Normal: "Normal", + Bottom: "Inferior", + None: "Nenhum", + Timezone: "Fuso horário", + "Search Engine Visibility": "Visibilidade do mecanismo de pesquisa", + "Allow indexing": "Permitir Indexação", + "Discourage search engines from indexing site": "Desencorajar que motores de busca indexem o site", + "Change Password": "Mudar senha", + "Current Password": "Senha atual", + "New Password": "Nova Senha", + "Repeat New Password": "Repetir Nova Senha", + "Update Password": "Atualizar Senha", + "Disable Auth": "Desativar Autenticação", + "Enable Auth": "Ativar Autenticação", + "disableauth.message1": "Tens a certeza que queres desativar a autenticação?", + "disableauth.message2": "Isso é para alguém que tem autenticação de terceiros em frente ao 'UpTime Kuma' como o Cloudflare Access.", + "Please use this option carefully!": "Por favor, utiliza esta opção com cuidado.", + Logout: "Logout", + Leave: "Sair", + "I understand, please disable": "Eu entendo, por favor desativa.", + Confirm: "Confirmar", + Yes: "Sim", + No: "Não", + Username: "Utilizador", + Password: "Senha", + "Remember me": "Lembra-me", + Login: "Autenticar", + "No Monitors, please": "Nenhum monitor, por favor", + "add one": "adicionar um", + "Notification Type": "Tipo de Notificação", + Email: "Email", + Test: "Testar", + "Certificate Info": "Info. do Certificado ", + "Resolver Server": "Resolver Servidor", + "Resource Record Type": "Tipo de registro de aplicação", + "Last Result": "Último resultado", + "Create your admin account": "Cria a tua conta de admin", + "Repeat Password": "Repete a senha", + "Import Backup": "Importar Backup", + "Export Backup": "Exportar Backup", + Export: "Exportar", + Import: "Importar", + respTime: "Tempo de Resp. (ms)", + notAvailableShort: "N/A", + "Default enabled": "Padrão habilitado", + "Apply on all existing monitors": "Aplicar em todos os monitores existentes", + Create: "Criar", + "Clear Data": "Limpar Dados", + Events: "Eventos", + Heartbeats: "Heartbeats", + "Auto Get": "Obter Automático", + backupDescription: "Podes fazer backup de todos os monitores e todas as notificações num arquivo JSON.", + backupDescription2: "OBS: Os dados do histórico e do evento não estão incluídos.", + backupDescription3: "Dados confidenciais, como tokens de notificação, estão incluídos no arquivo de exportação, mantem-no com cuidado.", + alertNoFile: "Seleciona um arquivo para importar.", + alertWrongFileType: "Seleciona um arquivo JSON.", + "Clear all statistics": "Limpar todas as estatísticas", + "Skip existing": "Saltar existente", + Overwrite: "Sobrescrever", + Options: "Opções", + "Keep both": "Manter os dois", + "Verify Token": "Verificar Token", + "Setup 2FA": "Configurar 2FA", + "Enable 2FA": "Ativar 2FA", + "Disable 2FA": "Desativar 2FA", + "2FA Settings": "Configurações do 2FA ", + "Two Factor Authentication": "Autenticação de Dois Fatores", + Active: "Ativo", + Inactive: "Inativo", + Token: "Token", + "Show URI": "Mostrar URI", + Tags: "Tag", + "Add New below or Select...": "Adicionar Novo abaixo ou Selecionar ...", + "Tag with this name already exist.": "Já existe uma etiqueta com este nome.", + "Tag with this value already exist.": "Já existe uma etiqueta com este valor.", + color: "cor", + "value (optional)": "valor (opcional)", + Gray: "Cinza", + Red: "Vermelho", + Orange: "Laranja", + Green: "Verde", + Blue: "Azul", + Indigo: "Índigo", + Purple: "Roxo", + Pink: "Rosa", + "Search...": "Pesquisa...", + "Avg. Ping": "Ping Médio.", + "Avg. Response": "Resposta Média. ", + "Status Page": "Página de Status", + "Status Pages": "Página de Status", + "Entry Page": "Página de entrada", + statusPageNothing: "Nada aqui, por favor, adiciona um grupo ou monitor.", + "No Services": "Nenhum Serviço", + "All Systems Operational": "Todos os Serviços Operacionais", + "Partially Degraded Service": "Serviço parcialmente degradados", + "Degraded Service": "Serviço Degradado", + "Add Group": "Adicionar Grupo", + "Add a monitor": "Adicionar um monitor", + "Edit Status Page": "Editar Página de Status", + "Go to Dashboard": "Ir para o dashboard", + telegram: "Telegram", + webhook: "Webhook", + smtp: "Email (SMTP)", + discord: "Discord", + teams: "Microsoft Teams", + signal: "Signal", + gotify: "Gotify", + slack: "Slack", + "rocket.chat": "Rocket.chat", + pushover: "Pushover", + pushy: "Pushy", + octopush: "Octopush", + promosms: "PromoSMS", + lunasea: "LunaSea", + apprise: "Apprise (Support 50+ Notification services)", + pushbullet: "Pushbullet", + line: "Line Messenger", + mattermost: "Mattermost", +}; diff --git a/src/languages/th-TH.js b/src/languages/th-TH.js index a573206b..92c4eb80 100644 --- a/src/languages/th-TH.js +++ b/src/languages/th-TH.js @@ -518,4 +518,5 @@ export default { "Go back to the previous page.": "กลับไปที่หน้าก่อนหน้า", "Coming Soon": "เร็ว ๆ นี้", wayToGetClickSendSMSToken: "คุณสามารถรับ API Username และ API Key ได้จาก {0}", + wayToGetLineNotifyToken: "คุณสามารถรับ access token ได้จาก {0}", }; diff --git a/src/languages/uk-UA.js b/src/languages/uk-UA.js index 51802a39..cc01793c 100644 --- a/src/languages/uk-UA.js +++ b/src/languages/uk-UA.js @@ -1,5 +1,5 @@ export default { - languageName: "Український", + languageName: "Українська", checkEverySecond: "Перевірка кожні {0} секунд", retriesDescription: "Максимальна кількість спроб перед позначенням сервісу як недоступного та надсиланням повідомлення", ignoreTLSError: "Ігнорувати помилку TLS/SSL для сайтів HTTPS", @@ -7,11 +7,11 @@ export default { maxRedirectDescription: "Максимальна кількість перенаправлень. Поставте 0, щоб вимкнути перенаправлення.", acceptedStatusCodesDescription: "Виберіть коди статусів для визначення доступності сервісу.", passwordNotMatchMsg: "Повторення паролю не збігається.", - notificationDescription: "Прив'яжіть повідомлення до моніторів.", + notificationDescription: "Прив'яжіть сповіщення до моніторів.", keywordDescription: "Пошук слова в чистому HTML або JSON-відповіді (чутливо до регістру)", pauseDashboardHome: "Пауза", deleteMonitorMsg: "Ви дійсно хочете видалити цей монітор?", - deleteNotificationMsg: "Ви дійсно хочете видалити це повідомлення для всіх моніторів?", + deleteNotificationMsg: "Ви дійсно хочете видалити це сповіщення для всіх моніторів?", resolverserverDescription: "Cloudflare є сервером за замовчуванням. Ви завжди можете змінити цей сервер.", rrtypeDescription: "Виберіть тип ресурсного запису, який ви хочете відстежувати", pauseMonitorMsg: "Ви дійсно хочете поставити на паузу?", @@ -54,7 +54,7 @@ export default { Keyword: "Ключове слово", "Friendly Name": "Ім'я", URL: "URL", - Hostname: "Ім'я хоста", + Hostname: "Адреса хоста", Port: "Порт", "Heartbeat Interval": "Частота опитування", Retries: "Спроб", @@ -63,7 +63,7 @@ export default { "Max. Redirects": "Макс. кількість перенаправлень", "Accepted Status Codes": "Припустимі коди статусу", Save: "Зберегти", - Notifications: "Повідомлення", + Notifications: "Сповіщення", "Not available, please setup.": "Доступних сповіщень немає, необхідно створити.", "Setup Notification": "Створити сповіщення", Light: "Світла", @@ -100,7 +100,7 @@ export default { "No Monitors, please": "Моніторів немає, будь ласка", "No Monitors": "Монітори відсутні", "add one": "створіть новий", - "Notification Type": "Тип повідомлення", + "Notification Type": "Тип сповіщення", Email: "Пошта", Test: "Перевірка", "Certificate Info": "Інформація про сертифікат", @@ -119,7 +119,7 @@ export default { Events: "Події", Heartbeats: "Опитування", "Auto Get": "Авто-отримання", - enableDefaultNotificationDescription: "Для кожного нового монітора це повідомлення буде включено за замовчуванням. Ви все ще можете відключити повідомлення в кожному моніторі окремо.", + enableDefaultNotificationDescription: "Для кожного нового монітора це сповіщення буде включено за замовчуванням. Ви все ще можете відключити сповіщення в кожному моніторі окремо.", "Default enabled": "Використовувати за промовчанням", "Also apply to existing monitors": "Застосувати до існуючих моніторів", Export: "Експорт", @@ -170,7 +170,7 @@ export default { Purple: "Пурпурний", Pink: "Рожевий", "Search...": "Пошук...", - "Avg. Ping": "Середнє значення пінгу", + "Avg. Ping": "Середній пінг", "Avg. Response": "Середній час відповіді", "Entry Page": "Головна сторінка", statusPageNothing: "Тут порожньо. Додайте групу або монітор.", @@ -210,7 +210,7 @@ export default { "Push URL": "URL пуша", needPushEvery: "До цієї URL необхідно звертатися кожні {0} секунд", pushOptionalParams: "Опціональні параметри: {0}", - defaultNotificationName: "Моє повідомлення {notification} ({number})", + defaultNotificationName: "Моє сповіщення {notification} ({number})", here: "тут", Required: "Потрібно", "Bot Token": "Токен бота", @@ -257,7 +257,7 @@ export default { "User Key": "Ключ користувача", Device: "Пристрій", "Message Title": "Заголовок повідомлення", - "Notification Sound": "Звук повідомлення", + "Notification Sound": "Звук сповіщення", "More info on:": "Більше інформації: {0}", pushoverDesc1: "Екстренний пріоритет (2) має таймуут повтору за замовчуванням 30 секунд і закінчується через 1 годину.", pushoverDesc2: "Якщо ви бажаєте надсилати повідомлення різним пристроям, необхідно заповнити поле Пристрій.", @@ -354,7 +354,7 @@ export default { "No consecutive dashes --": "Заборонено використовувати тире --", "HTTP Options": "HTTP Опції", Authentication: "Аутентифікація", - "HTTP Basic Auth": "HTTP Авторизація", + "HTTP Basic Auth": "Базова HTTP", PushByTechulus: "Push by Techulus", clicksendsms: "ClickSend SMS", GoogleChat: "Google Chat (тільки Google Workspace)", @@ -392,4 +392,139 @@ export default { alertaAlertState: "Стан алерту", alertaRecoverState: "Стан відновлення", deleteStatusPageMsg: "Дійсно хочете видалити цю сторінку статусів?", + Proxies: "Проксі", + default: "За замовчуванням", + enabled: "Активно", + setAsDefault: "Встановити за замовчуванням", + deleteProxyMsg: "Ви впевнені, що хочете видалити цей проксі для всіх моніторів?", + proxyDescription: "Щоб функціонувати, монітору потрібно призначити проксі.", + enableProxyDescription: "Цей проксі не впливатиме на запити моніторингу, доки його не буде активовано. Ви можете контролювати тимчасове відключення проксі з усіх моніторів за статусом активації.", + setAsDefaultProxyDescription: "Цей проксі буде ввімкнено за умовчанням для нових моніторів. Ви все одно можете вимкнути проксі окремо для кожного монітора.", + Invalid: "Недійсний", + AccessKeyId: "AccessKey ID", + SecretAccessKey: "AccessKey Secret", + PhoneNumbers: "PhoneNumbers", + TemplateCode: "TemplateCode", + SignName: "SignName", + "Sms template must contain parameters: ": "Шаблон смс повинен містити параметри: ", + "Bark Endpoint": "Bark Endpoint", + WebHookUrl: "WebHookUrl", + SecretKey: "SecretKey", + "For safety, must use secret key": "Для безпеки необхідно використовувати секретний ключ", + "Device Token": "Токен пристрою", + Platform: "Платформа", + iOS: "iOS", + Android: "Android", + Huawei: "Huawei", + High: "Високий", + Retry: "Повтор", + Topic: "Тема", + "WeCom Bot Key": "WeCom Bot ключ", + "Setup Proxy": "Налаштувати проксі", + "Proxy Protocol": "Протокол проксі", + "Proxy Server": "Проксі-сервер", + "Proxy server has authentication": "Проксі-сервер має аутентифікацію", + User: "Користувач", + Installed: "Встановлено", + "Not installed": "Не встановлено", + Running: "Запущено", + "Not running": "Не запущено", + "Remove Token": "Видалити токен", + Start: "Запустити", + Stop: "Зупинити", + "Uptime Kuma": "Uptime Kuma", + Slug: "Slug", + "Accept characters:": "Прийняти символи:", + startOrEndWithOnly: "Починається або закінчується лише {0}", + "No consecutive dashes": "Немає послідовних тире", + "The slug is already taken. Please choose another slug.": "The slug is already taken. Please choose another slug.", + "No Proxy": "Без проксі", + "Page Not Found": "Сторінку не знайдено", + "Reverse Proxy": "Реверсивний проксі", + wayToGetCloudflaredURL: "(Завантажити Cloudflare з {0})", + cloudflareWebsite: "Веб-сайт Cloudflare", + "Message:": "Повідомлення:", + "Don't know how to get the token? Please read the guide:": "Не знаєте, як отримати токен? Прочитайте посібник:", + "The current connection may be lost if you are currently connecting via Cloudflare Tunnel. Are you sure want to stop it? Type your current password to confirm it.": "Поточне з’єднання може бути втрачено, якщо ви зараз під’єднуєтеся через Cloudflare Tunnel. Ви дійсно хочете зробити це? Для підтвердження введіть поточний пароль.", + "Other Software": "Інше програмне забезпечення", + "For example: nginx, Apache and Traefik.": "Наприклад: nginx, Apache and Traefik.", + "Please read": "Будь ласка, прочитайте", + "Subject:": "Тема:", + "Valid To:": "Дійсний до:", + "Days Remaining:": "Залишилось днів:", + "Issuer:": "Емітент:", + "Fingerprint:": "Відбиток:", + "No status pages": "Немає сторінок статусу", + "Domain Name Expiry Notification": "Сповіщення про закінчення терміну дії доменного імені", + Proxy: "Проксі", + "Date Created": "Дата створення", + onebotHttpAddress: "OneBot адреса HTTP", + onebotMessageType: "OneBot тип повідомлення", + onebotGroupMessage: "Група", + onebotPrivateMessage: "Приватне", + onebotUserOrGroupId: "Група/Користувач ID", + onebotSafetyTips: "Для безпеки необхідно встановити маркер доступу", + "PushDeer Key": "PushDeer ключ", + "Footer Text": "Текст нижнього колонтитула", + "Show Powered By": "Показувати платформу", + "Domain Names": "Доменні імена", + signedInDisp: "Ви ввійшли як {0}", + signedInDispDisabled: "Авторизація вимкнена.", + "Certificate Expiry Notification": "Сповіщення про закінчення терміну дії сертифіката", + "API Username": "Користувач API", + "API Key": "Ключ API", + "Recipient Number": "Номер одержувача", + "From Name/Number": "Від Ім'я/Номер", + "Leave blank to use a shared sender number.": "Залиште поле порожнім, щоб використовувати спільний номер відправника.", + "Octopush API Version": "Octopush API версія", + "Legacy Octopush-DM": "Legacy Octopush-DM", + "endpoint": "кінцева точка", + octopushAPIKey: "\"Ключ API\" з облікових даних HTTP API в панелі керування", + octopushLogin: "\"Ім'я користувача\" з облікових даних HTTP API на панелі керування", + promosmsLogin: "API Логін", + promosmsPassword: "API Пароль", + "pushoversounds pushover": "Pushover (по замовчуванню)", + "pushoversounds bike": "Bike", + "pushoversounds bugle": "Bugle", + "pushoversounds cashregister": "Cash Register", + "pushoversounds classical": "Classical", + "pushoversounds cosmic": "Cosmic", + "pushoversounds falling": "Falling", + "pushoversounds gamelan": "Gamelan", + "pushoversounds incoming": "Incoming", + "pushoversounds intermission": "Intermission", + "pushoversounds magic": "Magic", + "pushoversounds mechanical": "Mechanical", + "pushoversounds pianobar": "Piano Bar", + "pushoversounds siren": "Siren", + "pushoversounds spacealarm": "Space Alarm", + "pushoversounds tugboat": "Tug Boat", + "pushoversounds alien": "Alien Alarm (long)", + "pushoversounds climb": "Climb (long)", + "pushoversounds persistent": "Persistent (long)", + "pushoversounds echo": "Pushover Echo (long)", + "pushoversounds updown": "Up Down (long)", + "pushoversounds vibrate": "Vibrate Only", + "pushoversounds none": "None (silent)", + pushyAPIKey: "Секретний ключ API", + pushyToken: "Токен пристрою", + "Using a Reverse Proxy?": "Використовувати зворотній проксі?", + "Check how to config it for WebSocket": "Перевірте, як налаштувати його для WebSocket", + "Steam Game Server": "Ігровий сервер Steam", + "Most likely causes:": "Найімовірніші причини:", + "The resource is no longer available.": "Ресурс більше не доступний.", + "There might be a typing error in the address.": "Можливо, в адресі є помилка.", + "What you can try:": "Що ви можете спробувати:", + "Retype the address.": "Повторно введіть адресу.", + "Go back to the previous page.": "Повернутися на попередню сторінку.", + "Coming Soon": "Незабаром", + wayToGetClickSendSMSToken: "Ви можете отримати ім’я користувача API та ключ API з {0} .", + "Connection String": "Рядок підключення", + "Query": "Запит", + settingsCertificateExpiry: "Закінчення терміну дії сертифіката TLS", + certificationExpiryDescription: "Запуск сповіщення для HTTPS моніторів коли до закінчення терміну дії TLS сертифіката:", + "ntfy Topic": "ntfy Тема", + "Domain": "Домен", + "Workstation": "Робоча станція", + disableCloudflaredNoAuthMsg: "Ви перебуваєте в режимі без авторизації, пароль не потрібен.", }; diff --git a/src/languages/zh-TW.js b/src/languages/zh-TW.js index ace32e17..be87c540 100644 --- a/src/languages/zh-TW.js +++ b/src/languages/zh-TW.js @@ -13,6 +13,7 @@ export default { pauseDashboardHome: "暫停", deleteMonitorMsg: "您確定要刪除此監測器嗎?", deleteNotificationMsg: "您確定要為所有監測器刪除此通知嗎?", + dnsPortDescription: "DNS 伺服器連接埠。預設為 53。您可以隨時變更連接埠。", resolverserverDescription: "Cloudflare 為預設伺服器。您可以隨時更換解析伺服器。", rrtypeDescription: "選擇您想要監測的資源記錄類型", pauseMonitorMsg: "您確定要暫停嗎?", @@ -332,6 +333,8 @@ export default { info: "資訊", warning: "警告", danger: "危險", + error: "錯誤", + critical: "嚴重", primary: "主要", light: "淺色", dark: "暗色", @@ -372,6 +375,13 @@ export default { smtpDkimHashAlgo: "雜湊演算法 (選填)", smtpDkimheaderFieldNames: "要簽署的郵件標頭 (選填)", smtpDkimskipFields: "不簽署的郵件標頭 (選填)", + wayToGetPagerDutyKey: "您可以前往服務 -> 服務目錄 -> (選取服務) -> 整合 -> 新增整合以取得。您可以搜尋 \"Events API V2\"。詳細資訊 {0}", + "Integration Key": "整合金鑰", + "Integration URL": "整合網址", + "Auto resolve or acknowledged": "自動解決或認可", + "do nothing": "不進行任何操作", + "auto acknowledged": "自動認可", + "auto resolve": "自動解決", gorush: "Gorush", alerta: "Alerta", alertaApiEndpoint: "API 端點", @@ -465,4 +475,65 @@ export default { "Footer Text": "頁尾文字", "Show Powered By": "顯示技術支援文字", "Domain Names": "網域名稱", + signedInDisp: "以 {0} 身分登入", + signedInDispDisabled: "驗證已停用。", + "Certificate Expiry Notification": "憑證到期通知", + "API Username": "API 使用者名稱", + "API Key": "API 金鑰", + "Recipient Number": "收件者號碼", + "From Name/Number": "來自名字/號碼", + "Leave blank to use a shared sender number.": "留空以使用共享寄件人號碼。", + "Octopush API Version": "Octopush API 版本", + "Legacy Octopush-DM": "舊版 Octopush-DM", + "endpoint": "端", + octopushAPIKey: "\"API key\" from HTTP API credentials in control panel", + octopushLogin: "\"Login\" from HTTP API credentials in control panel", + promosmsLogin: "API 登入名稱", + promosmsPassword: "API 密碼", + "pushoversounds pushover": "Pushover (預設)", + "pushoversounds bike": "車鈴", + "pushoversounds bugle": "號角", + "pushoversounds cashregister": "收銀機", + "pushoversounds classical": "古典", + "pushoversounds cosmic": "宇宙", + "pushoversounds falling": "下落", + "pushoversounds gamelan": "甘美朗", + "pushoversounds incoming": "來電", + "pushoversounds intermission": "中場休息", + "pushoversounds magic": "魔法", + "pushoversounds mechanical": "機械", + "pushoversounds pianobar": "Piano Bar", + "pushoversounds siren": "Siren", + "pushoversounds spacealarm": "Space Alarm", + "pushoversounds tugboat": "汽笛", + "pushoversounds alien": "外星鬧鐘 (長)", + "pushoversounds climb": "爬升 (長)", + "pushoversounds persistent": "持續 (長)", + "pushoversounds echo": "Pushover 回音 (長)", + "pushoversounds updown": "上下 (長)", + "pushoversounds vibrate": "僅震動", + "pushoversounds none": "無 (靜音)", + pushyAPIKey: "API 密鑰", + pushyToken: "裝置權杖", + "Show update if available": "顯示可用更新", + "Also check beta release": "檢查 Beta 版", + "Using a Reverse Proxy?": "正在使用反向代理?", + "Check how to config it for WebSocket": "查看如何為 WebSocket 設定", + "Steam Game Server": "Steam 遊戲伺服器", + "Most likely causes:": "可能原因:", + "The resource is no longer available.": "資源已不可用。", + "There might be a typing error in the address.": "網址可能有誤。", + "What you can try:": "您可以嘗試:", + "Retype the address.": "重新輸入網址。", + "Go back to the previous page.": "返回上一頁。", + "Coming Soon": "即將推出", + wayToGetClickSendSMSToken: "您可以從 {0} 取得 API 使用者名稱和金鑰。", + "Connection String": "連線字串", + "Query": "查詢", + settingsCertificateExpiry: "TLS 憑證到期", + certificationExpiryDescription: "TLS 將於 X 天後到期時觸發 HTTPS 監測器通知:", + "ntfy Topic": "ntfy 主題", + "Domain": "網域", + "Workstation": "工作站", + disableCloudflaredNoAuthMsg: "您處於無驗證模式。無須輸入密碼。", }; diff --git a/src/layouts/Layout.vue b/src/layouts/Layout.vue index aea58bf1..57519f55 100644 --- a/src/layouts/Layout.vue +++ b/src/layouts/Layout.vue @@ -77,7 +77,7 @@
-
@@ -168,15 +171,21 @@ - -