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; |
@ -1,7 +1,12 @@ |
|||||
<template> |
<template> |
||||
<router-view /> |
<router-view /> |
||||
</template> |
</template> |
||||
|
|
||||
<script> |
<script> |
||||
export default {} |
import { setPageLocale } from "./util-frontend"; |
||||
|
export default { |
||||
|
created() { |
||||
|
setPageLocale(); |
||||
|
}, |
||||
|
}; |
||||
</script> |
</script> |
||||
|
@ -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> |
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue