Mikhail5555
3 years ago
committed by
GitHub
156 changed files with 23042 additions and 10463 deletions
@ -0,0 +1,35 @@ |
|||
# This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node |
|||
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions |
|||
|
|||
name: Auto Test |
|||
|
|||
on: |
|||
push: |
|||
branches: [ master ] |
|||
pull_request: |
|||
branches: [ master ] |
|||
|
|||
jobs: |
|||
auto-test: |
|||
runs-on: ${{ matrix.os }} |
|||
|
|||
strategy: |
|||
matrix: |
|||
os: [macos-latest, ubuntu-latest, windows-latest] |
|||
node-version: [14.x, 16.x] |
|||
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/ |
|||
|
|||
steps: |
|||
- uses: actions/checkout@v2 |
|||
|
|||
- name: Use Node.js ${{ matrix.node-version }} |
|||
uses: actions/setup-node@v2 |
|||
with: |
|||
node-version: ${{ matrix.node-version }} |
|||
cache: 'npm' |
|||
- run: npm run install-legacy |
|||
- run: npm run build |
|||
- run: npm test |
|||
env: |
|||
HEADLESS_TEST: 1 |
|||
JUST_FOR_TEST: ${{ secrets.JUST_FOR_TEST }} |
@ -0,0 +1,11 @@ |
|||
const config = {}; |
|||
|
|||
if (process.env.TEST_FRONTEND) { |
|||
config.presets = ["@babel/preset-env"]; |
|||
} |
|||
|
|||
if (process.env.TEST_BACKEND) { |
|||
config.plugins = ["babel-plugin-rewire"]; |
|||
} |
|||
|
|||
module.exports = config; |
@ -0,0 +1,5 @@ |
|||
module.exports = { |
|||
"rootDir": "..", |
|||
"testRegex": "./test/backend.spec.js", |
|||
}; |
|||
|
@ -0,0 +1,5 @@ |
|||
module.exports = { |
|||
"rootDir": "..", |
|||
"testRegex": "./test/frontend.spec.js", |
|||
}; |
|||
|
@ -0,0 +1,6 @@ |
|||
module.exports = { |
|||
"launch": { |
|||
"headless": process.env.HEADLESS_TEST || false, |
|||
"userDataDir": "./data/test-chrome-profile", |
|||
} |
|||
}; |
@ -0,0 +1,11 @@ |
|||
module.exports = { |
|||
"verbose": true, |
|||
"preset": "jest-puppeteer", |
|||
"globals": { |
|||
"__DEV__": true |
|||
}, |
|||
"testRegex": "./test/e2e.spec.js", |
|||
"rootDir": "..", |
|||
"testTimeout": 30000, |
|||
}; |
|||
|
@ -0,0 +1,24 @@ |
|||
import legacy from "@vitejs/plugin-legacy"; |
|||
import vue from "@vitejs/plugin-vue"; |
|||
import { defineConfig } from "vite"; |
|||
|
|||
const postCssScss = require("postcss-scss"); |
|||
const postcssRTLCSS = require("postcss-rtlcss"); |
|||
|
|||
// https://vitejs.dev/config/
|
|||
export default defineConfig({ |
|||
plugins: [ |
|||
vue(), |
|||
legacy({ |
|||
targets: ["ie > 11"], |
|||
additionalLegacyPolyfills: ["regenerator-runtime/runtime"] |
|||
}) |
|||
], |
|||
css: { |
|||
postcss: { |
|||
"parser": postCssScss, |
|||
"map": false, |
|||
"plugins": [postcssRTLCSS] |
|||
} |
|||
}, |
|||
}); |
@ -0,0 +1,13 @@ |
|||
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db. |
|||
BEGIN TRANSACTION; |
|||
|
|||
ALTER TABLE monitor |
|||
ADD method TEXT default 'GET' not null; |
|||
|
|||
ALTER TABLE monitor |
|||
ADD body TEXT default null; |
|||
|
|||
ALTER TABLE monitor |
|||
ADD headers TEXT default null; |
|||
|
|||
COMMIT; |
@ -0,0 +1,7 @@ |
|||
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db. |
|||
BEGIN TRANSACTION; |
|||
|
|||
ALTER TABLE monitor |
|||
ADD push_token VARCHAR(20) DEFAULT NULL; |
|||
|
|||
COMMIT; |
@ -0,0 +1,8 @@ |
|||
# DON'T UPDATE TO alpine3.13, 1.14, see #41. |
|||
FROM node:14-alpine3.12 |
|||
WORKDIR /app |
|||
|
|||
# Install apprise, iputils for non-root ping, setpriv |
|||
RUN apk add --no-cache iputils setpriv dumb-init python3 py3-cryptography py3-pip py3-six py3-yaml py3-click py3-markdown py3-requests py3-requests-oauthlib && \ |
|||
pip3 --no-cache-dir install apprise && \ |
|||
rm -rf /root/.cache |
@ -0,0 +1,12 @@ |
|||
# DON'T UPDATE TO node:14-bullseye-slim, see #372. |
|||
# If the image changed, the second stage image should be changed too |
|||
FROM node:14-buster-slim |
|||
WORKDIR /app |
|||
|
|||
# Install Apprise, add sqlite3 cli for debugging in the future, iputils-ping for ping, util-linux for setpriv |
|||
# Stupid python3 and python3-pip actually install a lot of useless things into Debian, specific --no-install-recommends to skip them, make the base even smaller than alpine! |
|||
RUN apt update && \ |
|||
apt --yes --no-install-recommends install python3 python3-pip python3-cryptography python3-six python3-yaml python3-click python3-markdown python3-requests python3-requests-oauthlib \ |
|||
sqlite3 iputils-ping util-linux dumb-init && \ |
|||
pip3 --no-cache-dir install apprise && \ |
|||
rm -rf /var/lib/apt/lists/* |
@ -0,0 +1,51 @@ |
|||
FROM louislam/uptime-kuma:base-debian AS build |
|||
WORKDIR /app |
|||
|
|||
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=1 |
|||
|
|||
COPY . . |
|||
RUN npm ci && \ |
|||
npm run build && \ |
|||
npm ci --production && \ |
|||
chmod +x /app/extra/entrypoint.sh |
|||
|
|||
|
|||
FROM louislam/uptime-kuma:base-debian AS release |
|||
WORKDIR /app |
|||
|
|||
# Copy app files from build layer |
|||
COPY --from=build /app /app |
|||
|
|||
EXPOSE 3001 |
|||
VOLUME ["/app/data"] |
|||
HEALTHCHECK --interval=60s --timeout=30s --start-period=180s --retries=5 CMD node extra/healthcheck.js |
|||
ENTRYPOINT ["/usr/bin/dumb-init", "--", "extra/entrypoint.sh"] |
|||
CMD ["node", "server/server.js"] |
|||
|
|||
FROM release AS nightly |
|||
RUN npm run mark-as-nightly |
|||
|
|||
# Upload the artifact to Github |
|||
FROM louislam/uptime-kuma:base-debian AS upload-artifact |
|||
WORKDIR / |
|||
RUN apt update && \ |
|||
apt --yes install curl file |
|||
|
|||
ARG GITHUB_TOKEN |
|||
ARG TARGETARCH |
|||
ARG PLATFORM=debian |
|||
ARG VERSION=1.9.0 |
|||
ARG FILE=$PLATFORM-$TARGETARCH-$VERSION.tar.gz |
|||
ARG DIST=dist.tar.gz |
|||
|
|||
COPY --from=build /app /app |
|||
RUN chmod +x /app/extra/upload-github-release-asset.sh |
|||
|
|||
# Full Build |
|||
# RUN tar -zcvf $FILE app |
|||
# RUN /app/extra/upload-github-release-asset.sh github_api_token=$GITHUB_TOKEN owner=louislam repo=uptime-kuma tag=$VERSION filename=$FILE |
|||
|
|||
# Dist only |
|||
RUN cd /app && tar -zcvf $DIST dist |
|||
RUN /app/extra/upload-github-release-asset.sh github_api_token=$GITHUB_TOKEN owner=louislam repo=uptime-kuma tag=$VERSION filename=/app/$DIST |
|||
|
@ -0,0 +1,26 @@ |
|||
FROM louislam/uptime-kuma:base-alpine AS build |
|||
WORKDIR /app |
|||
|
|||
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=1 |
|||
|
|||
COPY . . |
|||
RUN npm ci && \ |
|||
npm run build && \ |
|||
npm ci --production && \ |
|||
chmod +x /app/extra/entrypoint.sh |
|||
|
|||
|
|||
FROM louislam/uptime-kuma:base-alpine AS release |
|||
WORKDIR /app |
|||
|
|||
# Copy app files from build layer |
|||
COPY --from=build /app /app |
|||
|
|||
EXPOSE 3001 |
|||
VOLUME ["/app/data"] |
|||
HEALTHCHECK --interval=60s --timeout=30s --start-period=180s --retries=5 CMD node extra/healthcheck.js |
|||
ENTRYPOINT ["/usr/bin/dumb-init", "--", "extra/entrypoint.sh"] |
|||
CMD ["node", "server/server.js"] |
|||
|
|||
FROM release AS nightly |
|||
RUN npm run mark-as-nightly |
@ -1,33 +0,0 @@ |
|||
# DON'T UPDATE TO node:14-bullseye-slim, see #372. |
|||
# If the image changed, the second stage image should be changed too |
|||
FROM node:14-buster-slim AS build |
|||
WORKDIR /app |
|||
|
|||
COPY . . |
|||
RUN npm install --legacy-peer-deps && \ |
|||
npm run build && \ |
|||
npm prune --production && \ |
|||
chmod +x /app/extra/entrypoint.sh |
|||
|
|||
|
|||
FROM node:14-buster-slim AS release |
|||
WORKDIR /app |
|||
|
|||
# Install Apprise, add sqlite3 cli for debugging in the future, iputils-ping for ping, util-linux for setpriv |
|||
RUN apt update && \ |
|||
apt --yes install python3 python3-pip python3-cryptography python3-six python3-yaml python3-click python3-markdown python3-requests python3-requests-oauthlib \ |
|||
sqlite3 iputils-ping util-linux && \ |
|||
pip3 --no-cache-dir install apprise && \ |
|||
rm -rf /var/lib/apt/lists/* |
|||
|
|||
# Copy app files from build layer |
|||
COPY --from=build /app /app |
|||
|
|||
EXPOSE 3001 |
|||
VOLUME ["/app/data"] |
|||
HEALTHCHECK --interval=60s --timeout=30s --start-period=180s --retries=5 CMD node extra/healthcheck.js |
|||
ENTRYPOINT ["extra/entrypoint.sh"] |
|||
CMD ["node", "server/server.js"] |
|||
|
|||
FROM release AS nightly |
|||
RUN npm run mark-as-nightly |
@ -1,30 +0,0 @@ |
|||
# DON'T UPDATE TO alpine3.13, 1.14, see #41. |
|||
FROM node:14-alpine3.12 AS build |
|||
WORKDIR /app |
|||
|
|||
COPY . . |
|||
RUN npm install --legacy-peer-deps && \ |
|||
npm run build && \ |
|||
npm prune --production && \ |
|||
chmod +x /app/extra/entrypoint.sh |
|||
|
|||
|
|||
FROM node:14-alpine3.12 AS release |
|||
WORKDIR /app |
|||
|
|||
# Install apprise, iputils for non-root ping, setpriv |
|||
RUN apk add --no-cache iputils setpriv python3 py3-cryptography py3-pip py3-six py3-yaml py3-click py3-markdown py3-requests py3-requests-oauthlib && \ |
|||
pip3 --no-cache-dir install apprise && \ |
|||
rm -rf /root/.cache |
|||
|
|||
# Copy app files from build layer |
|||
COPY --from=build /app /app |
|||
|
|||
EXPOSE 3001 |
|||
VOLUME ["/app/data"] |
|||
HEALTHCHECK --interval=60s --timeout=30s --start-period=180s --retries=5 CMD node extra/healthcheck.js |
|||
ENTRYPOINT ["extra/entrypoint.sh"] |
|||
CMD ["node", "server/server.js"] |
|||
|
|||
FROM release AS nightly |
|||
RUN npm run mark-as-nightly |
@ -0,0 +1,6 @@ |
|||
module.exports = { |
|||
apps: [{ |
|||
name: "uptime-kuma", |
|||
script: "./server/server.js", |
|||
}] |
|||
} |
@ -0,0 +1,57 @@ |
|||
console.log("Downloading dist"); |
|||
const https = require("https"); |
|||
const tar = require("tar"); |
|||
|
|||
const packageJSON = require("../package.json"); |
|||
const fs = require("fs"); |
|||
const version = packageJSON.version; |
|||
|
|||
const filename = "dist.tar.gz"; |
|||
|
|||
const url = `https://github.com/louislam/uptime-kuma/releases/download/${version}/${filename}`; |
|||
download(url); |
|||
|
|||
function download(url) { |
|||
console.log(url); |
|||
|
|||
https.get(url, (response) => { |
|||
if (response.statusCode === 200) { |
|||
console.log("Extracting dist..."); |
|||
|
|||
if (fs.existsSync("./dist")) { |
|||
|
|||
if (fs.existsSync("./dist-backup")) { |
|||
fs.rmdirSync("./dist-backup", { |
|||
recursive: true |
|||
}); |
|||
} |
|||
|
|||
fs.renameSync("./dist", "./dist-backup"); |
|||
} |
|||
|
|||
const tarStream = tar.x({ |
|||
cwd: "./", |
|||
}); |
|||
|
|||
tarStream.on("close", () => { |
|||
fs.rmdirSync("./dist-backup", { |
|||
recursive: true |
|||
}); |
|||
console.log("Done"); |
|||
}); |
|||
|
|||
tarStream.on("error", () => { |
|||
if (fs.existsSync("./dist-backup")) { |
|||
fs.renameSync("./dist-backup", "./dist"); |
|||
} |
|||
console.log("Done"); |
|||
}); |
|||
|
|||
response.pipe(tarStream); |
|||
} else if (response.statusCode === 302) { |
|||
download(response.headers.location); |
|||
} else { |
|||
console.log("dist not found"); |
|||
} |
|||
}); |
|||
} |
@ -0,0 +1,64 @@ |
|||
#!/usr/bin/env bash |
|||
# |
|||
# Author: Stefan Buck |
|||
# License: MIT |
|||
# https://gist.github.com/stefanbuck/ce788fee19ab6eb0b4447a85fc99f447 |
|||
# |
|||
# |
|||
# This script accepts the following parameters: |
|||
# |
|||
# * owner |
|||
# * repo |
|||
# * tag |
|||
# * filename |
|||
# * github_api_token |
|||
# |
|||
# Script to upload a release asset using the GitHub API v3. |
|||
# |
|||
# Example: |
|||
# |
|||
# upload-github-release-asset.sh github_api_token=TOKEN owner=stefanbuck repo=playground tag=v0.1.0 filename=./build.zip |
|||
# |
|||
|
|||
# Check dependencies. |
|||
set -e |
|||
xargs=$(which gxargs || which xargs) |
|||
|
|||
# Validate settings. |
|||
[ "$TRACE" ] && set -x |
|||
|
|||
CONFIG=$@ |
|||
|
|||
for line in $CONFIG; do |
|||
eval "$line" |
|||
done |
|||
|
|||
# Define variables. |
|||
GH_API="https://api.github.com" |
|||
GH_REPO="$GH_API/repos/$owner/$repo" |
|||
GH_TAGS="$GH_REPO/releases/tags/$tag" |
|||
AUTH="Authorization: token $github_api_token" |
|||
WGET_ARGS="--content-disposition --auth-no-challenge --no-cookie" |
|||
CURL_ARGS="-LJO#" |
|||
|
|||
if [[ "$tag" == 'LATEST' ]]; then |
|||
GH_TAGS="$GH_REPO/releases/latest" |
|||
fi |
|||
|
|||
# Validate token. |
|||
curl -o /dev/null -sH "$AUTH" $GH_REPO || { echo "Error: Invalid repo, token or network issue!"; exit 1; } |
|||
|
|||
# Read asset tags. |
|||
response=$(curl -sH "$AUTH" $GH_TAGS) |
|||
|
|||
# Get ID of the asset based on given filename. |
|||
eval $(echo "$response" | grep -m 1 "id.:" | grep -w id | tr : = | tr -cd '[[:alnum:]]=') |
|||
[ "$id" ] || { echo "Error: Failed to get release id for tag: $tag"; echo "$response" | awk 'length($0)<100' >&2; exit 1; } |
|||
|
|||
# Upload asset |
|||
echo "Uploading asset... " |
|||
|
|||
# Construct url |
|||
GH_ASSET="https://uploads.github.com/repos/$owner/$repo/releases/$id/assets?name=$(basename $filename)" |
|||
|
|||
curl "$GITHUB_OAUTH_BASIC" --data-binary @"$filename" -H "Authorization: token $github_api_token" -H "Content-Type: application/octet-stream" $GH_ASSET |
File diff suppressed because it is too large
@ -0,0 +1,7 @@ |
|||
const args = require("args-parser")(process.argv); |
|||
const demoMode = args["demo"] || false; |
|||
|
|||
module.exports = { |
|||
args, |
|||
demoMode |
|||
}; |
@ -0,0 +1,31 @@ |
|||
const path = require("path"); |
|||
const Bree = require("bree"); |
|||
const { SHARE_ENV } = require("worker_threads"); |
|||
|
|||
const jobs = [ |
|||
{ |
|||
name: "clear-old-data", |
|||
interval: "at 03:14", |
|||
} |
|||
]; |
|||
|
|||
const initBackgroundJobs = function (args) { |
|||
const bree = new Bree({ |
|||
root: path.resolve("server", "jobs"), |
|||
jobs, |
|||
worker: { |
|||
env: SHARE_ENV, |
|||
workerData: args, |
|||
}, |
|||
workerMessageHandler: (message) => { |
|||
console.log("[Background Job]:", message); |
|||
} |
|||
}); |
|||
|
|||
bree.start(); |
|||
return bree; |
|||
}; |
|||
|
|||
module.exports = { |
|||
initBackgroundJobs |
|||
}; |
@ -0,0 +1,40 @@ |
|||
const { log, exit, connectDb } = require("./util-worker"); |
|||
const { R } = require("redbean-node"); |
|||
const { setSetting, setting } = require("../util-server"); |
|||
|
|||
const DEFAULT_KEEP_PERIOD = 180; |
|||
|
|||
(async () => { |
|||
await connectDb(); |
|||
|
|||
let period = await setting("keepDataPeriodDays"); |
|||
|
|||
// Set Default Period
|
|||
if (period == null) { |
|||
await setSetting("keepDataPeriodDays", DEFAULT_KEEP_PERIOD, "general"); |
|||
period = DEFAULT_KEEP_PERIOD; |
|||
} |
|||
|
|||
// Try parse setting
|
|||
let parsedPeriod; |
|||
try { |
|||
parsedPeriod = parseInt(period); |
|||
} catch (_) { |
|||
log("Failed to parse setting, resetting to default.."); |
|||
await setSetting("keepDataPeriodDays", DEFAULT_KEEP_PERIOD, "general"); |
|||
parsedPeriod = DEFAULT_KEEP_PERIOD; |
|||
} |
|||
|
|||
log(`Clearing Data older than ${parsedPeriod} days...`); |
|||
|
|||
try { |
|||
await R.exec( |
|||
"DELETE FROM heartbeat WHERE time < DATETIME('now', '-' || ? || ' days') ", |
|||
[parsedPeriod] |
|||
); |
|||
} catch (e) { |
|||
log(`Failed to clear old data: ${e.message}`); |
|||
} |
|||
|
|||
exit(); |
|||
})(); |
@ -0,0 +1,39 @@ |
|||
const { parentPort, workerData } = require("worker_threads"); |
|||
const Database = require("../database"); |
|||
const path = require("path"); |
|||
|
|||
const log = function (any) { |
|||
if (parentPort) { |
|||
parentPort.postMessage(any); |
|||
} |
|||
}; |
|||
|
|||
const exit = function (error) { |
|||
if (error && error != 0) { |
|||
process.exit(error); |
|||
} else { |
|||
if (parentPort) { |
|||
parentPort.postMessage("done"); |
|||
} else { |
|||
process.exit(0); |
|||
} |
|||
} |
|||
}; |
|||
|
|||
const connectDb = async function () { |
|||
const dbPath = path.join( |
|||
process.env.DATA_DIR || workerData["data-dir"] || "./data/" |
|||
); |
|||
|
|||
Database.init({ |
|||
"data-dir": dbPath, |
|||
}); |
|||
|
|||
await Database.connect(); |
|||
}; |
|||
|
|||
module.exports = { |
|||
log, |
|||
exit, |
|||
connectDb, |
|||
}; |
@ -0,0 +1,108 @@ |
|||
const NotificationProvider = require("./notification-provider"); |
|||
const { DOWN, UP } = require("../../src/util"); |
|||
const { default: axios } = require("axios"); |
|||
const Crypto = require("crypto"); |
|||
const qs = require("qs"); |
|||
|
|||
class AliyunSMS extends NotificationProvider { |
|||
name = "AliyunSMS"; |
|||
|
|||
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { |
|||
let okMsg = "Sent Successfully."; |
|||
|
|||
try { |
|||
if (heartbeatJSON != null) { |
|||
let msgBody = JSON.stringify({ |
|||
name: monitorJSON["name"], |
|||
time: heartbeatJSON["time"], |
|||
status: this.statusToString(heartbeatJSON["status"]), |
|||
msg: heartbeatJSON["msg"], |
|||
}); |
|||
if (this.sendSms(notification, msgBody)) { |
|||
return okMsg; |
|||
} |
|||
} else { |
|||
let msgBody = JSON.stringify({ |
|||
name: "", |
|||
time: "", |
|||
status: "", |
|||
msg: msg, |
|||
}); |
|||
if (this.sendSms(notification, msgBody)) { |
|||
return okMsg; |
|||
} |
|||
} |
|||
} catch (error) { |
|||
this.throwGeneralAxiosError(error); |
|||
} |
|||
} |
|||
|
|||
async sendSms(notification, msgbody) { |
|||
let params = { |
|||
PhoneNumbers: notification.phonenumber, |
|||
TemplateCode: notification.templateCode, |
|||
SignName: notification.signName, |
|||
TemplateParam: msgbody, |
|||
AccessKeyId: notification.accessKeyId, |
|||
Format: "JSON", |
|||
SignatureMethod: "HMAC-SHA1", |
|||
SignatureVersion: "1.0", |
|||
SignatureNonce: Math.random().toString(), |
|||
Timestamp: new Date().toISOString(), |
|||
Action: "SendSms", |
|||
Version: "2017-05-25", |
|||
}; |
|||
|
|||
params.Signature = this.sign(params, notification.secretAccessKey); |
|||
let config = { |
|||
method: "POST", |
|||
url: "http://dysmsapi.aliyuncs.com/", |
|||
headers: { |
|||
"Content-Type": "application/x-www-form-urlencoded", |
|||
}, |
|||
data: qs.stringify(params), |
|||
}; |
|||
|
|||
let result = await axios(config); |
|||
if (result.data.Message == "OK") { |
|||
return true; |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
/** Aliyun request sign */ |
|||
sign(param, AccessKeySecret) { |
|||
let param2 = {}; |
|||
let data = []; |
|||
|
|||
let oa = Object.keys(param).sort(); |
|||
|
|||
for (let i = 0; i < oa.length; i++) { |
|||
let key = oa[i]; |
|||
param2[key] = param[key]; |
|||
} |
|||
|
|||
for (let key in param2) { |
|||
data.push(`${encodeURIComponent(key)}=${encodeURIComponent(param2[key])}`); |
|||
} |
|||
|
|||
let StringToSign = `POST&${encodeURIComponent("/")}&${encodeURIComponent(data.join("&"))}`; |
|||
return Crypto |
|||
.createHmac("sha1", `${AccessKeySecret}&`) |
|||
.update(Buffer.from(StringToSign)) |
|||
.digest("base64"); |
|||
} |
|||
|
|||
statusToString(status) { |
|||
switch (status) { |
|||
case DOWN: |
|||
return "DOWN"; |
|||
case UP: |
|||
return "UP"; |
|||
default: |
|||
return status; |
|||
} |
|||
} |
|||
} |
|||
|
|||
module.exports = AliyunSMS; |
@ -0,0 +1,79 @@ |
|||
const NotificationProvider = require("./notification-provider"); |
|||
const { DOWN, UP } = require("../../src/util"); |
|||
const { default: axios } = require("axios"); |
|||
const Crypto = require("crypto"); |
|||
|
|||
class DingDing extends NotificationProvider { |
|||
name = "DingDing"; |
|||
|
|||
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { |
|||
let okMsg = "Sent Successfully."; |
|||
|
|||
try { |
|||
if (heartbeatJSON != null) { |
|||
let params = { |
|||
msgtype: "markdown", |
|||
markdown: { |
|||
title: monitorJSON["name"], |
|||
text: `## [${this.statusToString(heartbeatJSON["status"])}] \n > ${heartbeatJSON["msg"]} \n > Time(UTC):${heartbeatJSON["time"]}`, |
|||
} |
|||
}; |
|||
if (this.sendToDingDing(notification, params)) { |
|||
return okMsg; |
|||
} |
|||
} else { |
|||
let params = { |
|||
msgtype: "text", |
|||
text: { |
|||
content: msg |
|||
} |
|||
}; |
|||
if (this.sendToDingDing(notification, params)) { |
|||
return okMsg; |
|||
} |
|||
} |
|||
} catch (error) { |
|||
this.throwGeneralAxiosError(error); |
|||
} |
|||
} |
|||
|
|||
async sendToDingDing(notification, params) { |
|||
let timestamp = Date.now(); |
|||
|
|||
let config = { |
|||
method: "POST", |
|||
headers: { |
|||
"Content-Type": "application/json", |
|||
}, |
|||
url: `${notification.webHookUrl}×tamp=${timestamp}&sign=${encodeURIComponent(this.sign(timestamp, notification.secretKey))}`, |
|||
data: JSON.stringify(params), |
|||
}; |
|||
|
|||
let result = await axios(config); |
|||
if (result.data.errmsg == "ok") { |
|||
return true; |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
/** DingDing sign */ |
|||
sign(timestamp, secretKey) { |
|||
return Crypto |
|||
.createHmac("sha256", Buffer.from(secretKey, "utf8")) |
|||
.update(Buffer.from(`${timestamp}\n${secretKey}`, "utf8")) |
|||
.digest("base64"); |
|||
} |
|||
|
|||
statusToString(status) { |
|||
switch (status) { |
|||
case DOWN: |
|||
return "DOWN"; |
|||
case UP: |
|||
return "UP"; |
|||
default: |
|||
return status; |
|||
} |
|||
} |
|||
} |
|||
|
|||
module.exports = DingDing; |
@ -0,0 +1,83 @@ |
|||
const NotificationProvider = require("./notification-provider"); |
|||
const axios = require("axios"); |
|||
const { DOWN, UP } = require("../../src/util"); |
|||
|
|||
class Feishu extends NotificationProvider { |
|||
name = "Feishu"; |
|||
|
|||
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { |
|||
let okMsg = "Sent Successfully."; |
|||
let feishuWebHookUrl = notification.feishuWebHookUrl; |
|||
|
|||
try { |
|||
if (heartbeatJSON == null) { |
|||
let testdata = { |
|||
msg_type: "text", |
|||
content: { |
|||
text: msg, |
|||
}, |
|||
}; |
|||
await axios.post(feishuWebHookUrl, testdata); |
|||
return okMsg; |
|||
} |
|||
|
|||
if (heartbeatJSON["status"] == DOWN) { |
|||
let downdata = { |
|||
msg_type: "post", |
|||
content: { |
|||
post: { |
|||
zh_cn: { |
|||
title: "UptimeKuma Alert: " + monitorJSON["name"], |
|||
content: [ |
|||
[ |
|||
{ |
|||
tag: "text", |
|||
text: |
|||
"[Down] " + |
|||
heartbeatJSON["msg"] + |
|||
"\nTime (UTC): " + |
|||
heartbeatJSON["time"], |
|||
}, |
|||
], |
|||
], |
|||
}, |
|||
}, |
|||
}, |
|||
}; |
|||
await axios.post(feishuWebHookUrl, downdata); |
|||
return okMsg; |
|||
} |
|||
|
|||
if (heartbeatJSON["status"] == UP) { |
|||
let updata = { |
|||
msg_type: "post", |
|||
content: { |
|||
post: { |
|||
zh_cn: { |
|||
title: "UptimeKuma Alert: " + monitorJSON["name"], |
|||
content: [ |
|||
[ |
|||
{ |
|||
tag: "text", |
|||
text: |
|||
"[Up] " + |
|||
heartbeatJSON["msg"] + |
|||
"\nTime (UTC): " + |
|||
heartbeatJSON["time"], |
|||
}, |
|||
], |
|||
], |
|||
}, |
|||
}, |
|||
}, |
|||
}; |
|||
await axios.post(feishuWebHookUrl, updata); |
|||
return okMsg; |
|||
} |
|||
} catch (error) { |
|||
this.throwGeneralAxiosError(error); |
|||
} |
|||
} |
|||
} |
|||
|
|||
module.exports = Feishu; |
@ -0,0 +1,45 @@ |
|||
const NotificationProvider = require("./notification-provider"); |
|||
const axios = require("axios"); |
|||
const Crypto = require("crypto"); |
|||
const { debug } = require("../../src/util"); |
|||
|
|||
class Matrix extends NotificationProvider { |
|||
name = "matrix"; |
|||
|
|||
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { |
|||
let okMsg = "Sent Successfully."; |
|||
|
|||
const size = 20; |
|||
const randomString = encodeURIComponent( |
|||
Crypto |
|||
.randomBytes(size) |
|||
.toString("base64") |
|||
.slice(0, size) |
|||
); |
|||
|
|||
debug("Random String: " + randomString); |
|||
|
|||
const roomId = encodeURIComponent(notification.internalRoomId); |
|||
|
|||
debug("Matrix Room ID: " + roomId); |
|||
|
|||
try { |
|||
let config = { |
|||
headers: { |
|||
"Authorization": `Bearer ${notification.accessToken}`, |
|||
} |
|||
}; |
|||
let data = { |
|||
"msgtype": "m.text", |
|||
"body": msg |
|||
}; |
|||
|
|||
await axios.put(`${notification.homeserverUrl}/_matrix/client/r0/rooms/${roomId}/send/m.room.message/${randomString}`, data, config); |
|||
return okMsg; |
|||
} catch (error) { |
|||
this.throwGeneralAxiosError(error); |
|||
} |
|||
} |
|||
} |
|||
|
|||
module.exports = Matrix; |
@ -0,0 +1,41 @@ |
|||
const NotificationProvider = require("./notification-provider"); |
|||
const axios = require("axios"); |
|||
|
|||
class PromoSMS extends NotificationProvider { |
|||
|
|||
name = "promosms"; |
|||
|
|||
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { |
|||
let okMsg = "Sent Successfully."; |
|||
|
|||
try { |
|||
let config = { |
|||
headers: { |
|||
"Content-Type": "application/json", |
|||
"Authorization": "Basic " + Buffer.from(notification.promosmsLogin + ":" + notification.promosmsPassword).toString('base64'), |
|||
"Accept": "text/json", |
|||
} |
|||
}; |
|||
let data = { |
|||
"recipients": [ notification.promosmsPhoneNumber ], |
|||
//Lets remove non ascii char
|
|||
"text": msg.replace(/[^\x00-\x7F]/g, ""), |
|||
"type": Number(notification.promosmsSMSType), |
|||
"sender": notification.promosmsSenderName |
|||
}; |
|||
|
|||
let resp = await axios.post("https://promosms.com/api/rest/v3_2/sms", data, config); |
|||
|
|||
if (resp.data.response.status !== 0) { |
|||
let error = "Something gone wrong. Api returned " + resp.data.response.status + "."; |
|||
this.throwGeneralAxiosError(error); |
|||
} |
|||
|
|||
return okMsg; |
|||
} catch (error) { |
|||
this.throwGeneralAxiosError(error); |
|||
} |
|||
} |
|||
} |
|||
|
|||
module.exports = PromoSMS; |
@ -0,0 +1,5 @@ |
|||
html[lang='fa'] { |
|||
#app { |
|||
font-family: 'IRANSans', 'Iranian Sans','B Nazanin', 'Tahoma', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, segoe ui, Roboto, helvetica neue, Arial, noto sans, sans-serif, apple color emoji, segoe ui emoji, segoe ui symbol, noto color emoji; |
|||
} |
|||
} |
@ -0,0 +1,73 @@ |
|||
@import "vars.scss"; |
|||
@import "node_modules/vue-multiselect/dist/vue-multiselect"; |
|||
|
|||
.multiselect__tags { |
|||
border-radius: 1.5rem; |
|||
border: 1px solid #ced4da; |
|||
min-height: 38px; |
|||
padding: 6px 40px 0 8px; |
|||
} |
|||
|
|||
.multiselect--active .multiselect__tags { |
|||
border-radius: 1rem; |
|||
} |
|||
|
|||
.multiselect__option--highlight { |
|||
background: $primary !important; |
|||
} |
|||
|
|||
.multiselect__option--highlight::after { |
|||
background: $primary !important; |
|||
} |
|||
|
|||
.multiselect__tag { |
|||
border-radius: $border-radius; |
|||
margin-bottom: 0; |
|||
padding: 6px 26px 6px 10px; |
|||
background: $primary !important; |
|||
} |
|||
|
|||
.multiselect__placeholder { |
|||
font-size: 1rem; |
|||
padding-left: 6px; |
|||
padding-top: 0; |
|||
padding-bottom: 0; |
|||
margin-bottom: 0; |
|||
opacity: 0.67; |
|||
} |
|||
|
|||
.multiselect__input, |
|||
.multiselect__single { |
|||
line-height: 14px; |
|||
margin-bottom: 0; |
|||
} |
|||
|
|||
.dark { |
|||
.multiselect__tag { |
|||
color: $dark-font-color2; |
|||
} |
|||
|
|||
.multiselect__tags { |
|||
background-color: $dark-bg2; |
|||
border-color: $dark-border-color; |
|||
} |
|||
|
|||
.multiselect__input, |
|||
.multiselect__single { |
|||
background-color: $dark-bg2; |
|||
color: $dark-font-color; |
|||
} |
|||
|
|||
.multiselect__content-wrapper { |
|||
background-color: $dark-bg2; |
|||
border-color: $dark-border-color; |
|||
} |
|||
|
|||
.multiselect--above .multiselect__content-wrapper { |
|||
border-color: $dark-border-color; |
|||
} |
|||
|
|||
.multiselect__option--selected { |
|||
background-color: $dark-bg; |
|||
} |
|||
} |
@ -0,0 +1,52 @@ |
|||
<template> |
|||
<div> |
|||
<h4>{{ $t("Certificate Info") }}</h4> |
|||
{{ $t("Certificate Chain") }}: |
|||
<div |
|||
v-if="valid" |
|||
class="rounded d-inline-flex ms-2 text-white tag-valid" |
|||
> |
|||
{{ $t("Valid") }} |
|||
</div> |
|||
<div |
|||
v-if="!valid" |
|||
class="rounded d-inline-flex ms-2 text-white tag-invalid" |
|||
> |
|||
{{ $t("Invalid") }} |
|||
</div> |
|||
<certificate-info-row :cert="certInfo" /> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import CertificateInfoRow from "./CertificateInfoRow.vue"; |
|||
export default { |
|||
components: { |
|||
CertificateInfoRow, |
|||
}, |
|||
props: { |
|||
certInfo: { |
|||
type: Object, |
|||
required: true, |
|||
}, |
|||
valid: { |
|||
type: Boolean, |
|||
required: true, |
|||
}, |
|||
}, |
|||
}; |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
@import "../assets/vars.scss"; |
|||
|
|||
.tag-valid { |
|||
padding: 2px 25px; |
|||
background-color: $primary; |
|||
} |
|||
|
|||
.tag-invalid { |
|||
padding: 2px 25px; |
|||
background-color: $danger; |
|||
} |
|||
</style> |
@ -0,0 +1,122 @@ |
|||
<template> |
|||
<div> |
|||
<div class="d-flex flex-row align-items-center p-1 overflow-hidden"> |
|||
<div class="m-3 ps-3"> |
|||
<div class="cert-icon"> |
|||
<font-awesome-icon icon="file" /> |
|||
<font-awesome-icon class="award-icon" icon="award" /> |
|||
</div> |
|||
</div> |
|||
<div class="m-3"> |
|||
<table class="text-start"> |
|||
<tbody> |
|||
<tr class="my-3"> |
|||
<td class="px-3">Subject:</td> |
|||
<td>{{ formatSubject(cert.subject) }}</td> |
|||
</tr> |
|||
<tr class="my-3"> |
|||
<td class="px-3">Valid To:</td> |
|||
<td><Datetime :value="cert.validTo" /></td> |
|||
</tr> |
|||
<tr class="my-3"> |
|||
<td class="px-3">Days Remaining:</td> |
|||
<td>{{ cert.daysRemaining }}</td> |
|||
</tr> |
|||
<tr class="my-3"> |
|||
<td class="px-3">Issuer:</td> |
|||
<td>{{ formatSubject(cert.issuer) }}</td> |
|||
</tr> |
|||
<tr class="my-3"> |
|||
<td class="px-3">Fingerprint:</td> |
|||
<td>{{ cert.fingerprint }}</td> |
|||
</tr> |
|||
</tbody> |
|||
</table> |
|||
</div> |
|||
</div> |
|||
<div class="d-flex"> |
|||
<font-awesome-icon |
|||
v-if="cert.issuerCertificate" |
|||
class="m-2 ps-6 link-icon" |
|||
icon="link" |
|||
/> |
|||
</div> |
|||
<certificate-info-row |
|||
v-if="cert.issuerCertificate" |
|||
:cert="cert.issuerCertificate" |
|||
/> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import Datetime from "../components/Datetime.vue"; |
|||
export default { |
|||
name: "CertificateInfoRow", |
|||
components: { |
|||
Datetime, |
|||
}, |
|||
props: { |
|||
cert: { |
|||
type: Object, |
|||
required: true, |
|||
}, |
|||
}, |
|||
methods: { |
|||
formatSubject(subject) { |
|||
if (subject.O && subject.CN && subject.C) { |
|||
return `${subject.CN} - ${subject.O} (${subject.C})`; |
|||
} else if (subject.O && subject.CN) { |
|||
return `${subject.CN} - ${subject.O}`; |
|||
} else if (subject.CN) { |
|||
return subject.CN; |
|||
} else { |
|||
return "no info"; |
|||
} |
|||
}, |
|||
}, |
|||
}; |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
@import "../assets/vars.scss"; |
|||
|
|||
table { |
|||
overflow: hidden; |
|||
} |
|||
|
|||
.cert-icon { |
|||
position: relative; |
|||
font-size: 70px; |
|||
color: $link-color; |
|||
opacity: 0.5; |
|||
|
|||
.dark & { |
|||
color: $dark-font-color; |
|||
opacity: 0.3; |
|||
} |
|||
} |
|||
|
|||
.award-icon { |
|||
position: absolute; |
|||
font-size: 0.5em; |
|||
bottom: 20%; |
|||
left: 12%; |
|||
color: white; |
|||
|
|||
.dark & { |
|||
color: $dark-bg; |
|||
} |
|||
} |
|||
|
|||
.link-icon { |
|||
font-size: 20px; |
|||
margin-left: 50px !important; |
|||
color: $link-color; |
|||
opacity: 0.5; |
|||
|
|||
.dark & { |
|||
color: $dark-font-color; |
|||
opacity: 0.3; |
|||
} |
|||
} |
|||
</style> |
@ -0,0 +1,122 @@ |
|||
<template> |
|||
<div class="input-group"> |
|||
<input |
|||
:id="id" |
|||
ref="input" |
|||
v-model="model" |
|||
:type="type" |
|||
class="form-control" |
|||
:placeholder="placeholder" |
|||
:autocomplete="autocomplete" |
|||
:required="required" |
|||
:readonly="readonly" |
|||
:disabled="disabled" |
|||
> |
|||
|
|||
<a class="btn btn-outline-primary" @click="copyToClipboard(model)"> |
|||
<font-awesome-icon :icon="icon" /> |
|||
</a> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
|
|||
let timeout; |
|||
|
|||
export default { |
|||
props: { |
|||
id: { |
|||
type: String, |
|||
default: "" |
|||
}, |
|||
type: { |
|||
type: String, |
|||
default: "text" |
|||
}, |
|||
modelValue: { |
|||
type: String, |
|||
default: "" |
|||
}, |
|||
placeholder: { |
|||
type: String, |
|||
default: "" |
|||
}, |
|||
autocomplete: { |
|||
type: String, |
|||
default: undefined, |
|||
}, |
|||
required: { |
|||
type: Boolean |
|||
}, |
|||
readonly: { |
|||
type: String, |
|||
default: undefined, |
|||
}, |
|||
disabled: { |
|||
type: String, |
|||
default: undefined, |
|||
}, |
|||
}, |
|||
data() { |
|||
return { |
|||
visibility: "password", |
|||
icon: "copy", |
|||
}; |
|||
}, |
|||
computed: { |
|||
model: { |
|||
get() { |
|||
return this.modelValue; |
|||
}, |
|||
set(value) { |
|||
this.$emit("update:modelValue", value); |
|||
} |
|||
} |
|||
}, |
|||
created() { |
|||
|
|||
}, |
|||
methods: { |
|||
|
|||
showInput() { |
|||
this.visibility = "text"; |
|||
}, |
|||
|
|||
hideInput() { |
|||
this.visibility = "password"; |
|||
}, |
|||
|
|||
copyToClipboard(textToCopy) { |
|||
this.icon = "check"; |
|||
|
|||
clearTimeout(timeout); |
|||
timeout = setTimeout(() => { |
|||
this.icon = "copy"; |
|||
}, 3000); |
|||
|
|||
// navigator clipboard api needs a secure context (https) |
|||
if (navigator.clipboard && window.isSecureContext) { |
|||
// navigator clipboard api method' |
|||
return navigator.clipboard.writeText(textToCopy); |
|||
} else { |
|||
// text area method |
|||
let textArea = document.createElement("textarea"); |
|||
textArea.value = textToCopy; |
|||
// make the textarea out of viewport |
|||
textArea.style.position = "fixed"; |
|||
textArea.style.left = "-999999px"; |
|||
textArea.style.top = "-999999px"; |
|||
document.body.appendChild(textArea); |
|||
textArea.focus(); |
|||
textArea.select(); |
|||
return new Promise((res, rej) => { |
|||
// here the magic happens |
|||
document.execCommand("copy") ? res() : rej(); |
|||
textArea.remove(); |
|||
}); |
|||
} |
|||
} |
|||
|
|||
} |
|||
}; |
|||
</script> |
@ -0,0 +1,25 @@ |
|||
<template> |
|||
<div class="mb-3"> |
|||
<label for="accessKeyId" class="form-label">{{ $t("AccessKeyId") }}<span style="color: red;"><sup>*</sup></span></label> |
|||
<input id="accessKeyId" v-model="$parent.notification.accessKeyId" type="text" class="form-control" required> |
|||
|
|||
<label for="secretAccessKey" class="form-label">{{ $t("SecretAccessKey") }}<span style="color: red;"><sup>*</sup></span></label> |
|||
<input id="secretAccessKey" v-model="$parent.notification.secretAccessKey" type="text" class="form-control" required> |
|||
|
|||
<label for="phonenumber" class="form-label">{{ $t("Phonenumber") }}<span style="color: red;"><sup>*</sup></span></label> |
|||
<input id="phonenumber" v-model="$parent.notification.phonenumber" type="text" class="form-control" required> |
|||
|
|||
<label for="templateCode" class="form-label">{{ $t("TemplateCode") }}<span style="color: red;"><sup>*</sup></span></label> |
|||
<input id="templateCode" v-model="$parent.notification.templateCode" type="text" class="form-control" required> |
|||
|
|||
<label for="signName" class="form-label">{{ $t("SignName") }}<span style="color: red;"><sup>*</sup></span></label> |
|||
<input id="signName" v-model="$parent.notification.signName" type="text" class="form-control" required> |
|||
|
|||
<div class="form-text"> |
|||
<p>Sms template must contain parameters: <br> <code>${name} ${time} ${status} ${msg}</code></p> |
|||
<i18n-t tag="p" keypath="Read more:"> |
|||
<a href="https://help.aliyun.com/document_detail/101414.html" target="_blank">https://help.aliyun.com/document_detail/101414.html</a> |
|||
</i18n-t> |
|||
</div> |
|||
</div> |
|||
</template> |
@ -0,0 +1,35 @@ |
|||
<template> |
|||
<div class="mb-3"> |
|||
<label for="apprise-url" class="form-label">{{ $t("Apprise URL") }}</label> |
|||
<input id="apprise-url" v-model="$parent.notification.appriseURL" type="text" class="form-control" required> |
|||
<div class="form-text"> |
|||
<p>{{ $t("Example:", ["twilio://AccountSid:AuthToken@FromPhoneNo"]) }}</p> |
|||
<i18n-t tag="p" keypath="Read more:"> |
|||
<a href="https://github.com/caronc/apprise/wiki#notification-services" target="_blank">https://github.com/caronc/apprise/wiki#notification-services</a> |
|||
</i18n-t> |
|||
</div> |
|||
</div> |
|||
<div class="mb-3"> |
|||
<i18n-t tag="p" keypath="Status:"> |
|||
<span v-if="appriseInstalled" class="text-primary">{{ $t("appriseInstalled") }}</span> |
|||
<i18n-t v-else tag="span" keypath="appriseNotInstalled" class="text-danger"> |
|||
<a href="https://github.com/caronc/apprise" target="_blank">{{ $t("Read more") }}</a> |
|||
</i18n-t> |
|||
</i18n-t> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
data() { |
|||
return { |
|||
appriseInstalled: false |
|||
}; |
|||
}, |
|||
mounted() { |
|||
this.$root.getSocket().emit("checkApprise", (installed) => { |
|||
this.appriseInstalled = installed; |
|||
}); |
|||
}, |
|||
}; |
|||
</script> |
@ -0,0 +1,16 @@ |
|||
<template> |
|||
<div class="mb-3"> |
|||
<label for="WebHookUrl" class="form-label">{{ $t("WebHookUrl") }}<span style="color: red;"><sup>*</sup></span></label> |
|||
<input id="WebHookUrl" v-model="$parent.notification.webHookUrl" type="text" class="form-control" required> |
|||
|
|||
<label for="secretKey" class="form-label">{{ $t("SecretKey") }}<span style="color: red;"><sup>*</sup></span></label> |
|||
<input id="secretKey" v-model="$parent.notification.secretKey" type="text" class="form-control" required> |
|||
|
|||
<div class="form-text"> |
|||
<p>For safety, must use secret key</p> |
|||
<i18n-t tag="p" keypath="Read more:"> |
|||
<a href="https://developers.dingtalk.com/document/robots/custom-robot-access" target="_blank">https://developers.dingtalk.com/document/robots/custom-robot-access</a> |
|||
</i18n-t> |
|||
</div> |
|||
</div> |
|||
</template> |
@ -0,0 +1,19 @@ |
|||
<template> |
|||
<div class="mb-3"> |
|||
<label for="discord-webhook-url" class="form-label">{{ $t("Discord Webhook URL") }}</label> |
|||
<input id="discord-webhook-url" v-model="$parent.notification.discordWebhookUrl" type="text" class="form-control" required autocomplete="false"> |
|||
<div class="form-text"> |
|||
{{ $t("wayToGetDiscordURL") }} |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="mb-3"> |
|||
<label for="discord-username" class="form-label">{{ $t("Bot Display Name") }}</label> |
|||
<input id="discord-username" v-model="$parent.notification.discordUsername" type="text" class="form-control" autocomplete="false" :placeholder="$root.appName"> |
|||
</div> |
|||
|
|||
<div class="mb-3"> |
|||
<label for="discord-prefix-message" class="form-label">{{ $t("Prefix Custom Message") }}</label> |
|||
<input id="discord-prefix-message" v-model="$parent.notification.discordPrefixMessage" type="text" class="form-control" autocomplete="false" :placeholder="$t('Hello @everyone is...')"> |
|||
</div> |
|||
</template> |
@ -0,0 +1,15 @@ |
|||
<template> |
|||
<div class="mb-3"> |
|||
<label for="Feishu-WebHookUrl" class="form-label">{{ $t("Feishu WebHookUrl") }}<span style="color: red;"><sup>*</sup></span></label> |
|||
<input id="Feishu-WebHookUrl" v-model="$parent.notification.feishuWebHookUrl" type="text" class="form-control" required> |
|||
<div class="form-text"> |
|||
<p><span style="color: red;"><sup>*</sup></span>{{ $t("Required") }}</p> |
|||
</div> |
|||
<i18n-t tag="div" keypath="wayToGetTeamsURL" class="form-text"> |
|||
<a |
|||
href="https://www.feishu.cn/hc/zh-CN/articles/360024984973" |
|||
target="_blank" |
|||
>{{ $t("here") }}</a> |
|||
</i18n-t> |
|||
</div> |
|||
</template> |
@ -0,0 +1,32 @@ |
|||
<template> |
|||
<div class="mb-3"> |
|||
<label for="gotify-application-token" class="form-label">{{ $t("Application Token") }}</label> |
|||
<HiddenInput id="gotify-application-token" v-model="$parent.notification.gotifyapplicationToken" :required="true" autocomplete="one-time-code"></HiddenInput> |
|||
</div> |
|||
<div class="mb-3"> |
|||
<label for="gotify-server-url" class="form-label">{{ $t("Server URL") }}</label> |
|||
<div class="input-group mb-3"> |
|||
<input id="gotify-server-url" v-model="$parent.notification.gotifyserverurl" type="text" class="form-control" required> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="mb-3"> |
|||
<label for="gotify-priority" class="form-label">{{ $t("Priority") }}</label> |
|||
<input id="gotify-priority" v-model="$parent.notification.gotifyPriority" type="number" class="form-control" required min="0" max="10" step="1"> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import HiddenInput from "../HiddenInput.vue"; |
|||
|
|||
export default { |
|||
components: { |
|||
HiddenInput, |
|||
}, |
|||
mounted() { |
|||
if (typeof this.$parent.notification.gotifyPriority === "undefined") { |
|||
this.$parent.notification.gotifyPriority = 8; |
|||
} |
|||
}, |
|||
} |
|||
</script> |
@ -0,0 +1,29 @@ |
|||
<template> |
|||
<div class="mb-3"> |
|||
<label for="line-channel-access-token" class="form-label">{{ $t("Channel access token") }}</label> |
|||
<HiddenInput id="line-channel-access-token" v-model="$parent.notification.lineChannelAccessToken" :required="true" autocomplete="one-time-code"></HiddenInput> |
|||
</div> |
|||
<i18n-t tag="div" keypath="lineDevConsoleTo" class="form-text"> |
|||
<b>{{ $t("Basic Settings") }}</b> |
|||
</i18n-t> |
|||
<div class="mb-3" style="margin-top: 12px;"> |
|||
<label for="line-user-id" class="form-label">User ID</label> |
|||
<input id="line-user-id" v-model="$parent.notification.lineUserID" type="text" class="form-control" required> |
|||
</div> |
|||
<i18n-t tag="div" keypath="lineDevConsoleTo" class="form-text"> |
|||
<b>{{ $t("Messaging API") }}</b> |
|||
</i18n-t> |
|||
<i18n-t tag="div" keypath="wayToGetLineChannelToken" class="form-text" style="margin-top: 8px;"> |
|||
<a href="https://developers.line.biz/console/" target="_blank">{{ $t("Line Developers Console") }}</a> |
|||
</i18n-t> |
|||
</template> |
|||
|
|||
<script> |
|||
import HiddenInput from "../HiddenInput.vue"; |
|||
|
|||
export default { |
|||
components: { |
|||
HiddenInput, |
|||
}, |
|||
}; |
|||
</script> |
@ -0,0 +1,9 @@ |
|||
<template> |
|||
<div class="mb-3"> |
|||
<label for="lunasea-device" class="form-label">{{ $t("LunaSea Device ID") }}<span style="color: red;"><sup>*</sup></span></label> |
|||
<input id="lunasea-device" v-model="$parent.notification.lunaseaDevice" type="text" class="form-control" required> |
|||
<div class="form-text"> |
|||
<p><span style="color: red;"><sup>*</sup></span>{{ $t("Required") }}</p> |
|||
</div> |
|||
</div> |
|||
</template> |
@ -0,0 +1,34 @@ |
|||
<template> |
|||
<div class="mb-3"> |
|||
<label for="homeserver-url" class="form-label">{{ $t("matrixHomeserverURL") }}</label><span style="color: red;"><sup>*</sup></span> |
|||
<input id="homeserver-url" v-model="$parent.notification.homeserverUrl" type="text" class="form-control" :required="true"> |
|||
</div> |
|||
<div class="mb-3"> |
|||
<label for="internal-room-id" class="form-label">{{ $t("Internal Room Id") }}</label><span style="color: red;"><sup>*</sup></span> |
|||
<input id="internal-room-id" v-model="$parent.notification.internalRoomId" type="text" class="form-control" required="true"> |
|||
</div> |
|||
<div class="mb-3"> |
|||
<label for="access-token" class="form-label">{{ $t("Access Token") }}</label><span style="color: red;"><sup>*</sup></span> |
|||
<HiddenInput id="access-token" v-model="$parent.notification.accessToken" :required="true" autocomplete="one-time-code" :maxlength="500"></HiddenInput> |
|||
</div> |
|||
|
|||
<div class="form-text"> |
|||
<span style="color: red;"><sup>*</sup></span>{{ $t("Required") }} |
|||
<p style="margin-top: 8px;"> |
|||
{{ $t("matrixDesc1") }} |
|||
</p> |
|||
<i18n-t tag="p" keypath="matrixDesc2" style="margin-top: 8px;"> |
|||
<code>curl -XPOST -d '{"type": "m.login.password", "identifier": {"user": "botusername", "type": "m.id.user"}, "password": "passwordforuser"}' "https://home.server/_matrix/client/r0/login"</code>. |
|||
</i18n-t> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import HiddenInput from "../HiddenInput.vue"; |
|||
|
|||
export default { |
|||
components: { |
|||
HiddenInput, |
|||
}, |
|||
}; |
|||
</script> |
@ -0,0 +1,32 @@ |
|||
<template> |
|||
<div class="mb-3"> |
|||
<label for="mattermost-webhook-url" class="form-label">{{ $t("Webhook URL") }}<span style="color:red;"><sup>*</sup></span></label> |
|||
<input id="mattermost-webhook-url" v-model="$parent.notification.mattermostWebhookUrl" type="text" class="form-control" required> |
|||
<label for="mattermost-username" class="form-label">{{ $t("Username") }}</label> |
|||
<input id="mattermost-username" v-model="$parent.notification.mattermostusername" type="text" class="form-control"> |
|||
<label for="mattermost-iconurl" class="form-label">{{ $t("Icon URL") }}</label> |
|||
<input id="mattermost-iconurl" v-model="$parent.notification.mattermosticonurl" type="text" class="form-control"> |
|||
<label for="mattermost-iconemo" class="form-label">{{ $t("Icon Emoji") }}</label> |
|||
<input id="mattermost-iconemo" v-model="$parent.notification.mattermosticonemo" type="text" class="form-control"> |
|||
<label for="mattermost-channel" class="form-label">{{ $t("Channel Name") }}</label> |
|||
<input id="mattermost-channel-name" v-model="$parent.notification.mattermostchannel" type="text" class="form-control"> |
|||
<div class="form-text"> |
|||
<span style="color:red;"><sup>*</sup></span>{{ $t("Required") }} |
|||
<i18n-t tag="p" keypath="aboutWebhooks" style="margin-top: 8px;"> |
|||
<a href="https://docs.mattermost.com/developer/webhooks-incoming.html" target="_blank">https://docs.mattermost.com/developer/webhooks-incoming.html</a> |
|||
</i18n-t> |
|||
<p style="margin-top: 8px;"> |
|||
{{ $t("aboutMattermostChannelName") }} |
|||
</p> |
|||
<p style="margin-top: 8px;"> |
|||
{{ $t("aboutKumaURL") }} |
|||
</p> |
|||
<p style="margin-top: 8px;"> |
|||
{{ $t("aboutIconURL") }} |
|||
</p> |
|||
<i18n-t tag="p" keypath="emojiCheatSheet" style="margin-top: 8px;"> |
|||
<a href="https://www.webfx.com/tools/emoji-cheat-sheet/" target="_blank">https://www.webfx.com/tools/emoji-cheat-sheet/</a> |
|||
</i18n-t> |
|||
</div> |
|||
</div> |
|||
</template> |
@ -0,0 +1,50 @@ |
|||
<template> |
|||
<div class="mb-3"> |
|||
<label for="octopush-version" class="form-label">Octopush API Version</label> |
|||
<select id="octopush-version" v-model="$parent.notification.octopushVersion" class="form-select"> |
|||
<option value="2">Octopush (endpoint: api.octopush.com)</option> |
|||
<option value="1">Legacy Octopush-DM (endpoint: www.octopush-dm.com)</option> |
|||
</select> |
|||
<div class="form-text"> |
|||
{{ $t("octopushLegacyHint") }} |
|||
</div> |
|||
</div> |
|||
<div class="mb-3"> |
|||
<label for="octopush-key" class="form-label">API KEY</label> |
|||
<HiddenInput id="octopush-key" v-model="$parent.notification.octopushAPIKey" :required="true" autocomplete="one-time-code"></HiddenInput> |
|||
<label for="octopush-login" class="form-label">API LOGIN</label> |
|||
<input id="octopush-login" v-model="$parent.notification.octopushLogin" type="text" class="form-control" required> |
|||
</div> |
|||
<div class="mb-3"> |
|||
<label for="octopush-type-sms" class="form-label">{{ $t("SMS Type") }}</label> |
|||
<select id="octopush-type-sms" v-model="$parent.notification.octopushSMSType" class="form-select"> |
|||
<option value="sms_premium">{{ $t("octopushTypePremium") }}</option> |
|||
<option value="sms_low_cost">{{ $t("octopushTypeLowCost") }}</option> |
|||
</select> |
|||
<i18n-t tag="div" keypath="Check octopush prices" class="form-text"> |
|||
<a href="https://octopush.com/tarifs-sms-international/" target="_blank">https://octopush.com/tarifs-sms-international/</a> |
|||
</i18n-t> |
|||
</div> |
|||
<div class="mb-3"> |
|||
<label for="octopush-phone-number" class="form-label">{{ $t("octopushPhoneNumber") }}</label> |
|||
<input id="octopush-phone-number" v-model="$parent.notification.octopushPhoneNumber" type="text" class="form-control" required> |
|||
</div> |
|||
<div class="mb-3"> |
|||
<label for="octopush-sender-name" class="form-label">{{ $t("octopushSMSSender") }}</label> |
|||
<input id="octopush-sender-name" v-model="$parent.notification.octopushSenderName" type="text" minlength="3" maxlength="11" class="form-control"> |
|||
</div> |
|||
|
|||
<i18n-t tag="p" keypath="More info on:" style="margin-top: 8px;"> |
|||
<a href="https://octopush.com/api-sms-documentation/envoi-de-sms/" target="_blank">https://octopush.com/api-sms-documentation/envoi-de-sms/</a> |
|||
</i18n-t> |
|||
</template> |
|||
|
|||
<script> |
|||
import HiddenInput from "../HiddenInput.vue"; |
|||
|
|||
export default { |
|||
components: { |
|||
HiddenInput, |
|||
}, |
|||
}; |
|||
</script> |
@ -0,0 +1,39 @@ |
|||
<template> |
|||
<div class="mb-3"> |
|||
<label for="promosms-login" class="form-label">API LOGIN</label> |
|||
<input id="promosms-login" v-model="$parent.notification.promosmsLogin" type="text" class="form-control" required> |
|||
<label for="promosms-key" class="form-label">API PASSWORD</label> |
|||
<HiddenInput id="promosms-key" v-model="$parent.notification.promosmsPassword" :required="true" autocomplete="one-time-code"></HiddenInput> |
|||
</div> |
|||
<div class="mb-3"> |
|||
<label for="promosms-type-sms" class="form-label">{{ $t("SMS Type") }}</label> |
|||
<select id="promosms-type-sms" v-model="$parent.notification.promosmsSMSType" class="form-select"> |
|||
<option value="0">{{ $t("promosmsTypeFlash") }}</option> |
|||
<option value="1">{{ $t("promosmsTypeEco") }}</option> |
|||
<option value="3">{{ $t("promosmsTypeFull") }}</option> |
|||
<option value="4">{{ $t("promosmsTypeSpeed") }}</option> |
|||
</select> |
|||
<div class="form-text"> |
|||
{{ $t("checkPrice", [$t("promosms")]) }} |
|||
<a href="https://promosms.com/cennik/" target="_blank">https://promosms.com/cennik/</a> |
|||
</div> |
|||
</div> |
|||
<div class="mb-3"> |
|||
<label for="promosms-phone-number" class="form-label">{{ $t("promosmsPhoneNumber") }}</label> |
|||
<input id="promosms-phone-number" v-model="$parent.notification.promosmsPhoneNumber" type="text" class="form-control" required> |
|||
</div> |
|||
<div class="mb-3"> |
|||
<label for="promosms-sender-name" class="form-label">{{ $t("promosmsSMSSender") }}</label> |
|||
<input id="promosms-sender-name" v-model="$parent.notification.promosmsSenderName" type="text" minlength="3" maxlength="11" class="form-control"> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import HiddenInput from "../HiddenInput.vue"; |
|||
|
|||
export default { |
|||
components: { |
|||
HiddenInput, |
|||
}, |
|||
}; |
|||
</script> |
@ -0,0 +1,20 @@ |
|||
<template> |
|||
<div class="mb-3"> |
|||
<label for="pushbullet-access-token" class="form-label">{{ $t("Access Token") }}</label> |
|||
<HiddenInput id="pushbullet-access-token" v-model="$parent.notification.pushbulletAccessToken" :required="true" autocomplete="one-time-code"></HiddenInput> |
|||
</div> |
|||
|
|||
<i18n-t tag="p" keypath="More info on:" style="margin-top: 8px;"> |
|||
<a href="https://docs.pushbullet.com" target="_blank">https://docs.pushbullet.com</a> |
|||
</i18n-t> |
|||
</template> |
|||
|
|||
<script> |
|||
import HiddenInput from "../HiddenInput.vue"; |
|||
|
|||
export default { |
|||
components: { |
|||
HiddenInput, |
|||
}, |
|||
}; |
|||
</script> |
@ -0,0 +1,67 @@ |
|||
<template> |
|||
<div class="mb-3"> |
|||
<label for="pushover-user" class="form-label">{{ $t("User Key") }}<span style="color: red;"><sup>*</sup></span></label> |
|||
<HiddenInput id="pushover-user" v-model="$parent.notification.pushoveruserkey" :required="true" autocomplete="one-time-code"></HiddenInput> |
|||
<label for="pushover-app-token" class="form-label">{{ $t("Application Token") }}<span style="color: red;"><sup>*</sup></span></label> |
|||
<HiddenInput id="pushover-app-token" v-model="$parent.notification.pushoverapptoken" :required="true" autocomplete="one-time-code"></HiddenInput> |
|||
<label for="pushover-device" class="form-label">{{ $t("Device") }}</label> |
|||
<input id="pushover-device" v-model="$parent.notification.pushoverdevice" type="text" class="form-control"> |
|||
<label for="pushover-device" class="form-label">{{ $t("Message Title") }}</label> |
|||
<input id="pushover-title" v-model="$parent.notification.pushovertitle" type="text" class="form-control"> |
|||
<label for="pushover-priority" class="form-label">{{ $t("Priority") }}</label> |
|||
<select id="pushover-priority" v-model="$parent.notification.pushoverpriority" class="form-select"> |
|||
<option>-2</option> |
|||
<option>-1</option> |
|||
<option>0</option> |
|||
<option>1</option> |
|||
<option>2</option> |
|||
</select> |
|||
<label for="pushover-sound" class="form-label">{{ $t("Notification Sound") }}</label> |
|||
<select id="pushover-sound" v-model="$parent.notification.pushoversounds" class="form-select"> |
|||
<option>pushover</option> |
|||
<option>bike</option> |
|||
<option>bugle</option> |
|||
<option>cashregister</option> |
|||
<option>classical</option> |
|||
<option>cosmic</option> |
|||
<option>falling</option> |
|||
<option>gamelan</option> |
|||
<option>incoming</option> |
|||
<option>intermission</option> |
|||
<option>mechanical</option> |
|||
<option>pianobar</option> |
|||
<option>siren</option> |
|||
<option>spacealarm</option> |
|||
<option>tugboat</option> |
|||
<option>alien</option> |
|||
<option>climb</option> |
|||
<option>persistent</option> |
|||
<option>echo</option> |
|||
<option>updown</option> |
|||
<option>vibrate</option> |
|||
<option>none</option> |
|||
</select> |
|||
<div class="form-text"> |
|||
<span style="color: red;"><sup>*</sup></span>{{ $t("Required") }} |
|||
<i18n-t tag="p" keypath="More info on:" style="margin-top: 8px;"> |
|||
<a href="https://pushover.net/api" target="_blank">https://pushover.net/api</a> |
|||
</i18n-t> |
|||
<p style="margin-top: 8px;"> |
|||
{{ $t("pushoverDesc1") }} |
|||
</p> |
|||
<p style="margin-top: 8px;"> |
|||
{{ $t("pushoverDesc2") }} |
|||
</p> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import HiddenInput from "../HiddenInput.vue"; |
|||
|
|||
export default { |
|||
components: { |
|||
HiddenInput, |
|||
}, |
|||
} |
|||
</script> |
@ -0,0 +1,26 @@ |
|||
<template> |
|||
<div class="mb-3"> |
|||
<label for="pushy-app-token" class="form-label">API_KEY</label> |
|||
<HiddenInput id="pushy-app-token" v-model="$parent.notification.pushyAPIKey" :required="true" autocomplete="one-time-code"></HiddenInput> |
|||
</div> |
|||
|
|||
<div class="mb-3"> |
|||
<label for="pushy-user-key" class="form-label">USER_TOKEN</label> |
|||
<div class="input-group mb-3"> |
|||
<HiddenInput id="pushy-user-key" v-model="$parent.notification.pushyToken" :required="true" autocomplete="one-time-code"></HiddenInput> |
|||
</div> |
|||
</div> |
|||
<i18n-t tag="p" keypath="More info on:" style="margin-top: 8px;"> |
|||
<a href="https://pushy.me/docs/api/send-notifications" target="_blank">https://pushy.me/docs/api/send-notifications</a> |
|||
</i18n-t> |
|||
</template> |
|||
|
|||
<script> |
|||
import HiddenInput from "../HiddenInput.vue"; |
|||
|
|||
export default { |
|||
components: { |
|||
HiddenInput, |
|||
}, |
|||
}; |
|||
</script> |
@ -0,0 +1,27 @@ |
|||
<template> |
|||
<div class="mb-3"> |
|||
<label for="rocket-webhook-url" class="form-label">{{ $t("Webhook URL") }}<span style="color: red;"><sup>*</sup></span></label> |
|||
<input id="rocket-webhook-url" v-model="$parent.notification.rocketwebhookURL" type="text" class="form-control" required> |
|||
<label for="rocket-username" class="form-label">{{ $t("Username") }}</label> |
|||
<input id="rocket-username" v-model="$parent.notification.rocketusername" type="text" class="form-control"> |
|||
<label for="rocket-iconemo" class="form-label">{{ $t("Icon Emoji") }}</label> |
|||
<input id="rocket-iconemo" v-model="$parent.notification.rocketiconemo" type="text" class="form-control"> |
|||
<label for="rocket-channel" class="form-label">{{ $t("Channel Name") }}</label> |
|||
<input id="rocket-channel-name" v-model="$parent.notification.rocketchannel" type="text" class="form-control"> |
|||
<div class="form-text"> |
|||
<span style="color: red;"><sup>*</sup></span>{{ $t("Required") }} |
|||
<i18n-t tag="p" keypath="aboutWebhooks" style="margin-top: 8px;"> |
|||
<a href="https://docs.rocket.chat/guides/administration/administration/integrations" target="_blank">https://api.slack.com/messaging/webhooks</a> |
|||
</i18n-t> |
|||
<p style="margin-top: 8px;"> |
|||
{{ $t("aboutChannelName", [$t("rocket.chat")]) }} |
|||
</p> |
|||
<p style="margin-top: 8px;"> |
|||
{{ $t("aboutKumaURL") }} |
|||
</p> |
|||
<i18n-t tag="p" keypath="emojiCheatSheet" style="margin-top: 8px;"> |
|||
<a href="https://www.webfx.com/tools/emoji-cheat-sheet/" target="_blank">https://www.webfx.com/tools/emoji-cheat-sheet/</a> |
|||
</i18n-t> |
|||
</div> |
|||
</div> |
|||
</template> |
@ -0,0 +1,34 @@ |
|||
<template> |
|||
<div class="mb-3"> |
|||
<label for="signal-url" class="form-label">{{ $t("Post URL") }}</label> |
|||
<input id="signal-url" v-model="$parent.notification.signalURL" type="url" pattern="https?://.+" class="form-control" required> |
|||
</div> |
|||
|
|||
<div class="mb-3"> |
|||
<label for="signal-number" class="form-label">{{ $t("Number") }}</label> |
|||
<input id="signal-number" v-model="$parent.notification.signalNumber" type="text" class="form-control" required> |
|||
</div> |
|||
|
|||
<div class="mb-3"> |
|||
<label for="signal-recipients" class="form-label">{{ $t("Recipients") }}</label> |
|||
<input id="signal-recipients" v-model="$parent.notification.signalRecipients" type="text" class="form-control" required> |
|||
|
|||
<div class="form-text"> |
|||
<p style="margin-top: 8px;"> |
|||
{{ $t("needSignalAPI") }} |
|||
</p> |
|||
|
|||
<p style="margin-top: 8px;"> |
|||
{{ $t("wayToCheckSignalURL") }} |
|||
</p> |
|||
|
|||
<p style="margin-top: 8px;"> |
|||
<a href="https://github.com/bbernhard/signal-cli-rest-api" target="_blank">https://github.com/bbernhard/signal-cli-rest-api</a> |
|||
</p> |
|||
|
|||
<p style="margin-top: 8px;"> |
|||
{{ $t("signalImportant") }} |
|||
</p> |
|||
</div> |
|||
</div> |
|||
</template> |
@ -0,0 +1,28 @@ |
|||
<template> |
|||
<div class="mb-3"> |
|||
<label for="slack-webhook-url" class="form-label">{{ $t("Webhook URL") }}<span style="color: red;"><sup>*</sup></span></label> |
|||
<input id="slack-webhook-url" v-model="$parent.notification.slackwebhookURL" type="text" class="form-control" required> |
|||
<label for="slack-username" class="form-label">{{ $t("Username") }}</label> |
|||
<input id="slack-username" v-model="$parent.notification.slackusername" type="text" class="form-control"> |
|||
<label for="slack-iconemo" class="form-label">{{ $t("Icon Emoji") }}</label> |
|||
<input id="slack-iconemo" v-model="$parent.notification.slackiconemo" type="text" class="form-control"> |
|||
<label for="slack-channel" class="form-label">{{ $t("Channel Name") }}</label> |
|||
<input id="slack-channel-name" v-model="$parent.notification.slackchannel" type="text" class="form-control"> |
|||
|
|||
<div class="form-text"> |
|||
<span style="color: red;"><sup>*</sup></span>{{ $t("Required") }} |
|||
<i18n-t tag="p" keypath="aboutWebhooks" style="margin-top: 8px;"> |
|||
<a href="https://api.slack.com/messaging/webhooks" target="_blank">https://api.slack.com/messaging/webhooks</a> |
|||
</i18n-t> |
|||
<p style="margin-top: 8px;"> |
|||
{{ $t("aboutChannelName", [$t("slack")]) }} |
|||
</p> |
|||
<p style="margin-top: 8px;"> |
|||
{{ $t("aboutKumaURL") }} |
|||
</p> |
|||
<i18n-t tag="p" keypath="emojiCheatSheet" style="margin-top: 8px;"> |
|||
<a href="https://www.webfx.com/tools/emoji-cheat-sheet/" target="_blank">https://www.webfx.com/tools/emoji-cheat-sheet/</a> |
|||
</i18n-t> |
|||
</div> |
|||
</div> |
|||
</template> |
@ -0,0 +1,28 @@ |
|||
<template> |
|||
<div class="mb-3"> |
|||
<label for="webhook-url" class="form-label">{{ $t("Post URL") }}</label> |
|||
<input id="webhook-url" v-model="$parent.notification.webhookURL" type="url" pattern="https?://.+" class="form-control" required> |
|||
</div> |
|||
|
|||
<div class="mb-3"> |
|||
<label for="webhook-content-type" class="form-label">{{ $t("Content Type") }}</label> |
|||
<select id="webhook-content-type" v-model="$parent.notification.webhookContentType" class="form-select" required> |
|||
<option value="json"> |
|||
application/json |
|||
</option> |
|||
<option value="form-data"> |
|||
multipart/form-data |
|||
</option> |
|||
</select> |
|||
|
|||
<div class="form-text"> |
|||
<p>{{ $t("webhookJsonDesc", ["\"application/json\""]) }}</p> |
|||
<i18n-t tag="p" keypath="webhookFormDataDesc"> |
|||
<template #multipart>"multipart/form-data"</template> |
|||
<template #decodeFunction> |
|||
<strong>json_decode($_POST['data'])</strong> |
|||
</template> |
|||
</i18n-t> |
|||
</div> |
|||
</div> |
|||
</template> |
@ -0,0 +1,54 @@ |
|||
import STMP from "./SMTP.vue" |
|||
import Telegram from "./Telegram.vue"; |
|||
import Discord from "./Discord.vue"; |
|||
import Webhook from "./Webhook.vue"; |
|||
import Signal from "./Signal.vue"; |
|||
import Gotify from "./Gotify.vue"; |
|||
import Slack from "./Slack.vue"; |
|||
import RocketChat from "./RocketChat.vue"; |
|||
import Teams from "./Teams.vue"; |
|||
import Pushover from "./Pushover.vue"; |
|||
import Pushy from "./Pushy.vue"; |
|||
import Octopush from "./Octopush.vue"; |
|||
import PromoSMS from "./PromoSMS.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"; |
|||
|
|||
/** |
|||
* Manage all notification form. |
|||
* |
|||
* @type { Record<string, any> } |
|||
*/ |
|||
const NotificationFormList = { |
|||
"telegram": Telegram, |
|||
"webhook": Webhook, |
|||
"smtp": STMP, |
|||
"discord": Discord, |
|||
"teams": Teams, |
|||
"signal": Signal, |
|||
"gotify": Gotify, |
|||
"slack": Slack, |
|||
"rocket.chat": RocketChat, |
|||
"pushover": Pushover, |
|||
"pushy": Pushy, |
|||
"octopush": Octopush, |
|||
"promosms": PromoSMS, |
|||
"lunasea": LunaSea, |
|||
"Feishu": Feishu, |
|||
"AliyunSMS": AliyunSMS, |
|||
"apprise": Apprise, |
|||
"pushbullet": Pushbullet, |
|||
"line": Line, |
|||
"mattermost": Mattermost, |
|||
"matrix": Matrix, |
|||
"DingDing": DingDing |
|||
} |
|||
|
|||
export default NotificationFormList |
@ -0,0 +1,200 @@ |
|||
export default { |
|||
languageName: "Български", |
|||
checkEverySecond: "Ще се извършва на всеки {0} секунди", |
|||
retryCheckEverySecond: "Ще се извършва на всеки {0} секунди", |
|||
retriesDescription: "Максимакен брой опити преди услугата да бъде маркирана като недостъпна и да бъде изпратено известие", |
|||
ignoreTLSError: "Игнорирай TLS/SSL грешки за HTTPS уебсайтове", |
|||
upsideDownModeDescription: "Обърни статуса от достъпен на недостъпен. Ако услугата е достъпна се вижда НЕДОСТЪПНА.", |
|||
maxRedirectDescription: "Максимален брой пренасочвания, които да бъдат следвани. Въведете 0 за да изключите пренасочване.", |
|||
acceptedStatusCodesDescription: "Изберете статус кодове, които се считат за успешен отговор.", |
|||
passwordNotMatchMsg: "Повторената парола не съвпада.", |
|||
notificationDescription: "Моля, задайте известието към монитор(и), за да функционира.", |
|||
keywordDescription: "Търси ключова дума в чист html или JSON отговор - чувствителна е към регистъра", |
|||
pauseDashboardHome: "Пауза", |
|||
deleteMonitorMsg: "Наистина ли желаете да изтриете този монитор?", |
|||
deleteNotificationMsg: "Наистина ли желаете да изтриете това известяване за всички монитори?", |
|||
resoverserverDescription: "Cloudflare е сървърът по подразбиране, но можете да го промените по всяко време.", |
|||
rrtypeDescription: "Изберете ресурсния запис, който желаете да наблюдавате", |
|||
pauseMonitorMsg: "Наистина ли желаете да поставите в режим пауза?", |
|||
enableDefaultNotificationDescription: "За всеки нов монитор това известяване ще бъде активирано по подразбиране. Можете да го изключите за всеки отделен монитор.", |
|||
clearEventsMsg: "Наистина ли желаете да изтриете всички събития за този монитор?", |
|||
clearHeartbeatsMsg: "Наистина ли желаете да изтриете всички записи за честотни проверки на този монитор?", |
|||
confirmClearStatisticsMsg: "Наистина ли желаете да изтриете всички статистически данни?", |
|||
importHandleDescription: "Изберете 'Пропусни съществуващите', ако желаете да пропуснете всеки монитор или известяване със същото име. 'Презапис' ще изтрие всеки съществуващ монитор и известяване.", |
|||
confirmImportMsg: "Сигурни ли сте, че желаете импортирането на архива? Моля, уверете се, че сте избрали правилната опция за импортиране.", |
|||
twoFAVerifyLabel: "Моля, въведете вашия токен код, за да проверите дали 2FA работи", |
|||
tokenValidSettingsMsg: "Токен кодът е валиден! Вече можете да запазите настройките за 2FA.", |
|||
confirmEnableTwoFAMsg: "Сигурни ли сте, че желаете да активирате 2FA?", |
|||
confirmDisableTwoFAMsg: "Сигурни ли сте, че желаете да изключите 2FA?", |
|||
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": "-часa", |
|||
Response: "Отговор", |
|||
Ping: "Пинг", |
|||
"Monitor Type": "Монитор тип", |
|||
Keyword: "Ключова дума", |
|||
"Friendly Name": "Псевдоним", |
|||
URL: "URL Адрес", |
|||
Hostname: "Име на хост", |
|||
Port: "Порт", |
|||
"Heartbeat Interval": "Честота на проверка", |
|||
Retries: "Повторни опити", |
|||
"Heartbeat Retry Interval": "Честота на повторните опити", |
|||
Advanced: "Разширени", |
|||
"Upside Down Mode": "Обърнат режим", |
|||
"Max. Redirects": "Макс. брой пренасочвания", |
|||
"Accepted Status Codes": "Допустими статус кодове", |
|||
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": "Повторете новата парола", |
|||
"Update Password": "Актуализирай парола", |
|||
"Disable Auth": "Изключи удостоверяване", |
|||
"Enable Auth": "Включи удостоверяване", |
|||
Logout: "Изход от профила", |
|||
Leave: "Отказ", |
|||
"I understand, please disable": "Разбирам. Моля, изключи", |
|||
Confirm: "Потвърдете", |
|||
Yes: "Да", |
|||
No: "Не", |
|||
Username: "Потребител", |
|||
Password: "Парола", |
|||
"Remember me": "Запомни ме", |
|||
Login: "Вход", |
|||
"No Monitors, please": "Моля, без монитори", |
|||
"add one": "добави един", |
|||
"Notification Type": "Тип известяване", |
|||
Email: "Имейл", |
|||
Test: "Тест", |
|||
"Certificate Info": "Информация за сертификат", |
|||
"Resolver Server": "Преобразуващ (DNS) сървър", |
|||
"Resource Record Type": "Тип запис", |
|||
"Last Result": "Последен резултат", |
|||
"Create your admin account": "Създаване на администриращ акаунт", |
|||
"Repeat Password": "Повторете паролата", |
|||
"Import Backup": "Импорт на архив", |
|||
"Export Backup": "Експорт на архив", |
|||
Export: "Експорт", |
|||
Import: "Импорт", |
|||
respTime: "Време за отговор (ms)", |
|||
notAvailableShort: "Няма", |
|||
"Default enabled": "Включен по подразбиране", |
|||
"Apply on all existing monitors": "Приложи върху всички съществуващи монитори", |
|||
Create: "Създай", |
|||
"Clear Data": "Изтрий данни", |
|||
Events: "Събития", |
|||
Heartbeats: "Проверки", |
|||
"Auto Get": "Автоматияно получаване", |
|||
backupDescription: "Можете да архивирате всички монитори и всички известия в JSON файл.", |
|||
backupDescription2: "PS: Данни за история и събития не са включени.", |
|||
backupDescription3: "Чувствителни данни, като токен кодове за известяване, се съдържат в експортирания файл. Моля, бъдете внимателни с неговото съхранение.", |
|||
alertNoFile: "Моля, изберете файл за импортиране.", |
|||
alertWrongFileType: "Моля, изберете JSON файл.", |
|||
"Clear all statistics": "Изтрий цялата статистика", |
|||
"Skip existing": "Пропусни съществуващите", |
|||
Overwrite: "Презапиши", |
|||
Options: "Опции", |
|||
"Keep both": "Запази двете", |
|||
"Verify Token": "Проверка на токен код", |
|||
"Setup 2FA": "Настройка 2FA", |
|||
"Enable 2FA": "Включи 2FA", |
|||
"Disable 2FA": "Изключи 2FA", |
|||
"2FA Settings": "Настройки 2FA", |
|||
"Two Factor Authentication": "Двуфакторно удостоверяване", |
|||
Active: "Активно", |
|||
Inactive: "Неактивно", |
|||
Token: "Токен код", |
|||
"Show URI": "Покажи URI", |
|||
Tags: "Етикети", |
|||
"Add New below or Select...": "Добавете нов по-долу или изберете...", |
|||
"Tag with this name already exist.": "Етикет с това име вече съществува.", |
|||
"Tag with this value already exist.": "Етикет с тази стойност вече съществува.", |
|||
color: "цвят", |
|||
"value (optional)": "стойност (по желание)", |
|||
Gray: "Сиво", |
|||
Red: "Червено", |
|||
Orange: "Оранжево", |
|||
Green: "Зелено", |
|||
Blue: "Синьо", |
|||
Indigo: "Индиго", |
|||
Purple: "Лилаво", |
|||
Pink: "Розово", |
|||
"Search...": "Търси...", |
|||
"Avg. Ping": "Ср. пинг", |
|||
"Avg. Response": "Ср. отговор", |
|||
"Entry Page": "Основна страница", |
|||
statusPageNothing: "Все още няма нищо тук. Моля, добавете група или монитор.", |
|||
"No Services": "Няма Услуги", |
|||
"All Systems Operational": "Всички услуги са достъпни", |
|||
"Partially Degraded Service": "Част от услугите са недостъпни", |
|||
"Degraded Service": "Всички услуги са недостъпни", |
|||
"Add Group": "Добави група", |
|||
"Add a monitor": "Добави монитор", |
|||
"Edit Status Page": "Редактиране Статус страница", |
|||
"Go to 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 (Поддържа 50+ услуги за инвестяване)", |
|||
pushbullet: "Pushbullet", |
|||
line: "Line Messenger", |
|||
mattermost: "Mattermost", |
|||
"Status Page": "Статус страница", |
|||
}; |
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue