Browse Source

Merge branch 'louislam:master' into master

pull/732/head
Daniel Patrón Gómez 4 years ago
committed by GitHub
parent
commit
8149e02c83
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 22
      .github/workflows/stale-bot
  2. 2
      CODE_OF_CONDUCT.md
  3. 12
      README.md
  4. 5
      docker/dockerfile
  5. 32
      kubernetes/README.md
  6. 10
      kubernetes/kustomization.yml
  7. 45
      kubernetes/uptime-kuma/deployment.yml
  8. 39
      kubernetes/uptime-kuma/ingressroute.yml
  9. 5
      kubernetes/uptime-kuma/kustomization.yml
  10. 10
      kubernetes/uptime-kuma/pvc.yml
  11. 13
      kubernetes/uptime-kuma/service.yml
  12. 12
      package.json
  13. 22
      server/database.js
  14. 42
      server/model/monitor.js
  15. 22
      server/server.js
  16. 4
      src/components/notifications/PromoSMS.vue
  17. 64
      src/languages/en.js
  18. 8
      src/mixins/socket.js
  19. 2
      src/pages/Details.vue
  20. 15
      src/pages/EditMonitor.vue
  21. 10
      src/pages/Settings.vue
  22. 2
      src/pages/Setup.vue
  23. 57
      src/util.js
  24. 66
      src/util.ts

22
.github/workflows/stale-bot

@ -0,0 +1,22 @@
name: 'Automatically close stale issues and PRs'
on:
schedule:
- cron: '0 0 * * *'
#Run once a day at midnight
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v4
with:
stale-issue-message: 'We are clearing up our old issues and your ticket has been open for 6 months with no activity. Remove stale label or comment or this will be closed in 7 days.'
stale-pr-message: 'We are clearing up our old Pull Requests and yours has been open for 6 months with no activity. Remove stale label or comment or this will be closed in 7 days.'
close-issue-message: 'This issue was closed because it has been stalled for 7 days with no activity.'
close-pr-message: 'This PR was closed because it has been stalled for 7 days with no activity.'
days-before-stale: 180
days-before-close: 7
exempt-issue-labels: 'News,Medium,High,discussion,bug,doc,'
exempt-pr-labels: 'awaiting-approval,work-in-progress,enhancement,'
exempt-issue-assignees: 'louislam'
exempt-pr-assignees: 'louislam'

2
CODE_OF_CONDUCT.md

@ -60,7 +60,7 @@ representative at an online or offline event.
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
louis@uptimekuma.louislam.net.
uptime@kuma.pet.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the

12
README.md

@ -16,7 +16,7 @@ Try it!
https://demo.uptime.kuma.pet
It is a 10 minutes live demo, all data will be deleted after that. The server is located at Tokyo, if you live far away from here, it may affact your experience. I suggest that you should install to try it.
It is a temporary live demo, all data will be deleted after 10 minutes. The server is located at Tokyo, so if you live far from there it may affect your experience. I suggest that you should install and try it out for the best demo experience.
VPS is sponsored by Uptime Kuma sponsors on [Open Collective](https://opencollective.com/uptime-kuma)! Thank you so much!
@ -25,7 +25,7 @@ VPS is sponsored by Uptime Kuma sponsors on [Open Collective](https://opencollec
* Monitoring uptime for HTTP(s) / TCP / Ping / DNS Record / Push.
* Fancy, Reactive, Fast UI/UX.
* Notifications via Telegram, Discord, Gotify, Slack, Pushover, Email (SMTP), and [70+ notification services, click here for the full list](https://github.com/louislam/uptime-kuma/tree/master/src/components/notifications).
* 20 seconds interval.
* 20 second intervals.
* [Multi Languages](https://github.com/louislam/uptime-kuma/tree/master/src/languages)
* Simple Status Page
* Ping Chart
@ -40,7 +40,7 @@ docker volume create uptime-kuma
docker run -d --restart=always -p 3001:3001 -v uptime-kuma:/app/data --name uptime-kuma louislam/uptime-kuma:1
```
Browse to http://localhost:3001 after started.
Browse to http://localhost:3001 after starting.
### 💪🏻 Without Docker
@ -58,11 +58,11 @@ npm run setup
node server/server.js
# (Recommended) Option 2. Run in background using PM2
# Install PM2 if you don't have: npm install pm2 -g
# Install PM2 if you don't have it: npm install pm2 -g
pm2 start server/server.js --name uptime-kuma
```
Browse to http://localhost:3001 after started.
Browse to http://localhost:3001 after starting.
### Advanced Installation
@ -124,7 +124,7 @@ You can discuss or ask for help in [Issues](https://github.com/louislam/uptime-k
### Subreddit
My Reddit account: louislamlam
You can mention me if you ask question on Reddit.
You can mention me if you ask a question on Reddit.
https://www.reddit.com/r/UptimeKuma/
## Contribute

5
docker/dockerfile

@ -31,14 +31,15 @@ WORKDIR /
RUN apt update && \
apt --yes install curl file
COPY --from=build /app /app
ARG VERSION=1.9.1
ARG GITHUB_TOKEN
ARG TARGETARCH
ARG PLATFORM=debian
ARG VERSION
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

32
kubernetes/README.md

@ -1,32 +0,0 @@
# Uptime-Kuma K8s Deployment
⚠ Warning: K8s deployment is provided by contributors. I have no experience with K8s and I can't fix error in the future. I only test Docker and Node.js. Use at your own risk.
## How does it work?
Kustomize is a tool which builds a complete deployment file for all config elements.
You can edit the files in the ```uptime-kuma``` folder except the ```kustomization.yml``` until you know what you're doing.
If you want to choose another namespace you can edit the ```kustomization.yml``` in the ```kubernetes```-Folder and change the ```namespace: uptime-kuma``` to something you like.
It creates a certificate with the specified Issuer and creates the Ingress for the Uptime-Kuma ClusterIP-Service.
## What do I have to edit?
You have to edit the ```ingressroute.yml``` to your needs.
This ingressroute.yml is for the [nginx-ingress-controller](https://kubernetes.github.io/ingress-nginx/) in combination with the [cert-manager](https://cert-manager.io/).
- Host
- Secrets and secret names
- (Cluster)Issuer (optional)
- The Version in the Deployment-File
- Update:
- Change to newer version and run the above commands, it will update the pods one after another
## How To use
- Install [kustomize](https://kubectl.docs.kubernetes.io/installation/kustomize/)
- Edit files mentioned above to your needs
- Run ```kustomize build > apply.yml```
- Run ```kubectl apply -f apply.yml```
Now you should see some k8s magic and Uptime-Kuma should be available at the specified address.

10
kubernetes/kustomization.yml

@ -1,10 +0,0 @@
namespace: uptime-kuma
namePrefix: uptime-kuma-
commonLabels:
app: uptime-kuma
bases:
- uptime-kuma

45
kubernetes/uptime-kuma/deployment.yml

@ -1,45 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
component: uptime-kuma
name: deployment
spec:
selector:
matchLabels:
component: uptime-kuma
replicas: 1
strategy:
type: Recreate
template:
metadata:
labels:
component: uptime-kuma
spec:
containers:
- name: app
image: louislam/uptime-kuma:1
ports:
- containerPort: 3001
volumeMounts:
- mountPath: /app/data
name: storage
livenessProbe:
exec:
command:
- node
- extra/healthcheck.js
initialDelaySeconds: 180
periodSeconds: 60
timeoutSeconds: 30
readinessProbe:
httpGet:
path: /
port: 3001
scheme: HTTP
volumes:
- name: storage
persistentVolumeClaim:
claimName: pvc

39
kubernetes/uptime-kuma/ingressroute.yml

@ -1,39 +0,0 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
kubernetes.io/ingress.class: nginx
cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
nginx.ingress.kubernetes.io/server-snippets: |
location / {
proxy_set_header Upgrade $http_upgrade;
proxy_http_version 1.1;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header Host $host;
proxy_set_header Connection "upgrade";
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade $http_upgrade;
proxy_cache_bypass $http_upgrade;
}
name: ingress
spec:
tls:
- hosts:
- example.com
secretName: example-com-tls
rules:
- host: example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: service
port:
number: 3001

5
kubernetes/uptime-kuma/kustomization.yml

@ -1,5 +0,0 @@
resources:
- deployment.yml
- service.yml
- ingressroute.yml
- pvc.yml

10
kubernetes/uptime-kuma/pvc.yml

@ -1,10 +0,0 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 4Gi

13
kubernetes/uptime-kuma/service.yml

@ -1,13 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: service
spec:
selector:
component: uptime-kuma
type: ClusterIP
ports:
- name: http
port: 3001
targetPort: 3001
protocol: TCP

12
package.json

@ -1,6 +1,6 @@
{
"name": "uptime-kuma",
"version": "1.8.0",
"version": "1.9.1",
"license": "MIT",
"repository": {
"type": "git",
@ -30,13 +30,13 @@
"build-docker": "npm run build-docker-debian && npm run build-docker-alpine",
"build-docker-alpine-base": "docker buildx build -f docker/alpine-base.dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:base-alpine . --push",
"build-docker-debian-base": "docker buildx build -f docker/debian-base.dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:base-debian . --push",
"build-docker-alpine": "docker buildx build -f docker/dockerfile-alpine --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:alpine -t louislam/uptime-kuma:1-alpine -t louislam/uptime-kuma:1.8.0-alpine --target release . --push",
"build-docker-debian": "docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma -t louislam/uptime-kuma:1 -t louislam/uptime-kuma:1.8.0 -t louislam/uptime-kuma:debian -t louislam/uptime-kuma:1-debian -t louislam/uptime-kuma:1.8.0-debian --target release . --push",
"build-docker-alpine": "docker buildx build -f docker/dockerfile-alpine --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:alpine -t louislam/uptime-kuma:1-alpine -t louislam/uptime-kuma:1.9.1-alpine --target release . --push",
"build-docker-debian": "docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma -t louislam/uptime-kuma:1 -t louislam/uptime-kuma:1.9.1 -t louislam/uptime-kuma:debian -t louislam/uptime-kuma:1-debian -t louislam/uptime-kuma:1.9.1-debian --target release . --push",
"build-docker-nightly": "docker buildx build -f docker/dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:nightly --target nightly . --push",
"build-docker-nightly-alpine": "docker buildx build -f dockerfile-alpine --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:nightly-alpine --target nightly . --push",
"build-docker-nightly-alpine": "docker buildx build -f docker/dockerfile-alpine --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:nightly-alpine --target nightly . --push",
"build-docker-nightly-amd64": "docker buildx build -f docker/dockerfile --platform linux/amd64 -t louislam/uptime-kuma:nightly-amd64 --target nightly . --push --progress plain",
"upload-artifacts": "docker buildx build --platform linux/amd64 -t louislam/uptime-kuma:upload-artifact --build-arg GITHUB_TOKEN --target upload-artifact . --progress plain",
"setup": "git checkout 1.8.0 && npm ci --production && npm run download-dist",
"upload-artifacts": "docker buildx build -f docker/dockerfile --platform linux/amd64 -t louislam/uptime-kuma:upload-artifact --build-arg GITHUB_TOKEN --target upload-artifact . --progress plain",
"setup": "git checkout 1.9.1 && npm ci --production && npm run download-dist",
"download-dist": "node extra/download-dist.js",
"update-version": "node extra/update-version.js",
"mark-as-nightly": "node extra/mark-as-nightly.js",

22
server/database.js

@ -53,7 +53,7 @@ class Database {
}
/**
* The finally version should be 10 after merged tag feature
* The final version should be 10 after merged tag feature
* @deprecated Use patchList for any new feature
*/
static latestVersion = 10;
@ -131,7 +131,7 @@ class Database {
console.info("Latest database version: " + this.latestVersion);
if (version === this.latestVersion) {
console.info("Database no need to patch");
console.info("Database patch not needed");
} else if (version > this.latestVersion) {
console.info("Warning: Database version is newer than expected");
} else {
@ -152,8 +152,8 @@ class Database {
await Database.close();
console.error(ex);
console.error("Start Uptime-Kuma failed due to patch db failed");
console.error("Please submit the bug report if you still encounter the problem after restart: https://github.com/louislam/uptime-kuma/issues");
console.error("Start Uptime-Kuma failed due to issue patching the database");
console.error("Please submit a bug report if you still encounter the problem after restart: https://github.com/louislam/uptime-kuma/issues");
this.restore();
process.exit(1);
@ -191,7 +191,7 @@ class Database {
await Database.close();
console.error(ex);
console.error("Start Uptime-Kuma failed due to patch db failed");
console.error("Start Uptime-Kuma failed due to issue patching the database");
console.error("Please submit the bug report if you still encounter the problem after restart: https://github.com/louislam/uptime-kuma/issues");
this.restore();
@ -232,7 +232,7 @@ class Database {
this.patched = true;
await this.importSQLFile("./db/" + sqlFilename);
databasePatchedFiles[sqlFilename] = true;
console.log(sqlFilename + " is patched successfully");
console.log(sqlFilename + " was patched successfully");
} else {
debug(sqlFilename + " is already patched, skip");
@ -287,7 +287,7 @@ class Database {
};
process.addListener("unhandledRejection", listener);
console.log("Closing DB");
console.log("Closing the database");
while (true) {
Database.noReject = true;
@ -297,7 +297,7 @@ class Database {
if (Database.noReject) {
break;
} else {
console.log("Waiting to close the db");
console.log("Waiting to close the database");
}
}
console.log("SQLite closed");
@ -312,7 +312,7 @@ class Database {
*/
static backup(version) {
if (! this.backupPath) {
console.info("Backup the db");
console.info("Backing up the database");
this.backupPath = this.dataDir + "kuma.db.bak" + version;
fs.copyFileSync(Database.path, this.backupPath);
@ -335,7 +335,7 @@ class Database {
*/
static restore() {
if (this.backupPath) {
console.error("Patch db failed!!! Restoring the backup");
console.error("Patching the database failed!!! Restoring the backup");
const shmPath = Database.path + "-shm";
const walPath = Database.path + "-wal";
@ -354,7 +354,7 @@ class Database {
fs.unlinkSync(walPath);
}
} catch (e) {
console.log("Restore failed, you may need to restore the backup manually");
console.log("Restore failed; you may need to restore the backup manually");
process.exit(1);
}

42
server/model/monitor.js

@ -7,7 +7,7 @@ dayjs.extend(timezone);
const axios = require("axios");
const { Prometheus } = require("../prometheus");
const { debug, UP, DOWN, PENDING, flipStatus, TimeLogger } = require("../../src/util");
const { tcping, ping, dnsResolve, checkCertificate, checkStatusCode, getTotalClientInRoom } = require("../util-server");
const { tcping, ping, dnsResolve, checkCertificate, checkStatusCode, getTotalClientInRoom, setting } = require("../util-server");
const { R } = require("redbean-node");
const { BeanModel } = require("redbean-node/dist/bean-model");
const { Notification } = require("../notification");
@ -272,6 +272,46 @@ class Monitor extends BeanModel {
return;
}
} else if (this.type === "steam") {
const steamApiUrl = "https://api.steampowered.com/IGameServersService/GetServerList/v1/";
const steamAPIKey = await setting("steamAPIKey");
const filter = `addr\\${this.hostname}:${this.port}`;
if (!steamAPIKey) {
throw new Error("Steam API Key not found");
}
let res = await axios.get(steamApiUrl, {
timeout: this.interval * 1000 * 0.8,
headers: {
"Accept": "*/*",
"User-Agent": "Uptime-Kuma/" + version,
},
httpsAgent: new https.Agent({
maxCachedSessions: 0, // Use Custom agent to disable session reuse (https://github.com/nodejs/node/issues/3940)
rejectUnauthorized: ! this.getIgnoreTls(),
}),
maxRedirects: this.maxredirects,
validateStatus: (status) => {
return checkStatusCode(status, this.getAcceptedStatuscodes());
},
params: {
filter: filter,
key: steamAPIKey,
}
});
if (res.data.response && res.data.response.servers && res.data.response.servers.length > 0) {
bean.status = UP;
bean.msg = res.data.response.servers[0].name;
try {
bean.ping = await ping(this.hostname);
} catch (_) { }
} else {
throw new Error("Server not found on Steam");
}
} else {
bean.msg = "Unknown Monitor Type";
bean.status = PENDING;

22
server/server.js

@ -77,12 +77,13 @@ const port = parseInt(process.env.UPTIME_KUMA_PORT || process.env.PORT || args.p
// SSL
const sslKey = process.env.UPTIME_KUMA_SSL_KEY || process.env.SSL_KEY || args["ssl-key"] || undefined;
const sslCert = process.env.UPTIME_KUMA_SSL_CERT || process.env.SSL_CERT || args["ssl-cert"] || undefined;
const disableFrameSameOrigin = !!process.env.UPTIME_KUMA_DISABLE_FRAME_SAMEORIGIN || args["disable-frame-sameorigin"] || false;
// 2FA / notp verification defaults
const twofa_verification_opts = {
"window": 1,
"time": 30
}
};
/**
* Run unit test after the server is ready
@ -119,6 +120,15 @@ const { statusPageSocketHandler } = require("./socket-handlers/status-page-socke
app.use(express.json());
// Global Middleware
app.use(function (req, res, next) {
if (!disableFrameSameOrigin) {
res.setHeader("X-Frame-Options", "SAMEORIGIN");
}
res.removeHeader("X-Powered-By");
next();
});
/**
* Total WebSocket client connected to server currently, no actual use
* @type {number}
@ -192,7 +202,7 @@ exports.entryPage = "dashboard";
const apiRouter = require("./routers/api-router");
app.use(apiRouter);
// Universal Route Handler, must be at the end of all express route.
// Universal Route Handler, must be at the end of all express routes.
app.get("*", async (_request, response) => {
if (_request.originalUrl.startsWith("/upload/")) {
response.status(404).send("File not found.");
@ -321,7 +331,7 @@ exports.entryPage = "dashboard";
]);
if (user.twofa_status == 0) {
let newSecret = await genSecret();
let newSecret = genSecret();
let encodedSecret = base32.encode(newSecret);
// Google authenticator doesn't like equal signs
@ -449,7 +459,7 @@ exports.entryPage = "dashboard";
socket.on("setup", async (username, password, callback) => {
try {
if ((await R.count("user")) !== 0) {
throw new Error("Uptime Kuma has been setup. If you want to setup again, please delete the database.");
throw new Error("Uptime Kuma has been initialized. If you want to run setup again, please delete the database.");
}
let user = R.dispense("user");
@ -1339,7 +1349,7 @@ async function initDatabase() {
fs.copyFileSync(Database.templatePath, Database.path);
}
console.log("Connecting to Database");
console.log("Connecting to the Database");
await Database.connect();
console.log("Connected");
@ -1439,7 +1449,7 @@ async function shutdownFunction(signal) {
}
function finalFunction() {
console.log("Graceful shutdown successfully!");
console.log("Graceful shutdown successful!");
}
gracefulShutdown(server, {

4
src/components/notifications/PromoSMS.vue

@ -10,8 +10,8 @@
<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="2">{{ $t("promosmsTypeFull") }}</option>
<option value="3">{{ $t("promosmsTypeSpeed") }}</option>
<option value="3">{{ $t("promosmsTypeFull") }}</option>
<option value="4">{{ $t("promosmsTypeSpeed") }}</option>
</select>
<div class="form-text">
{{ $t("checkPrice", [$t("promosms")]) }}

64
src/languages/en.js

@ -1,28 +1,28 @@
export default {
languageName: "English",
checkEverySecond: "Check every {0} seconds.",
retryCheckEverySecond: "Retry every {0} seconds.",
checkEverySecond: "Check every {0} seconds",
retryCheckEverySecond: "Retry every {0} seconds",
retriesDescription: "Maximum retries before the service is marked as down and a notification is sent",
ignoreTLSError: "Ignore TLS/SSL error for HTTPS websites",
upsideDownModeDescription: "Flip the status upside down. If the service is reachable, it is DOWN.",
maxRedirectDescription: "Maximum number of redirects to follow. Set to 0 to disable redirects.",
acceptedStatusCodesDescription: "Select status codes which are considered as a successful response.",
passwordNotMatchMsg: "The repeat password does not match.",
notificationDescription: "Please assign a notification to monitor(s) to get it to work.",
keywordDescription: "Search keyword in plain html or JSON response and it is case-sensitive",
notificationDescription: "Notifications must be assigned to a monitor to function.",
keywordDescription: "Search keyword in plain HTML or JSON response. The search is case-sensitive.",
pauseDashboardHome: "Pause",
deleteMonitorMsg: "Are you sure want to delete this monitor?",
deleteNotificationMsg: "Are you sure want to delete this notification for all monitors?",
resoverserverDescription: "Cloudflare is the default server, you can change the resolver server anytime.",
rrtypeDescription: "Select the RR-Type you want to monitor",
resoverserverDescription: "Cloudflare is the default server. You can change the resolver server anytime.",
rrtypeDescription: "Select the RR type you want to monitor",
pauseMonitorMsg: "Are you sure want to pause?",
enableDefaultNotificationDescription: "For every new monitor this notification will be enabled by default. You can still disable the notification separately for each monitor.",
enableDefaultNotificationDescription: "This notification will be enabled by default for new monitors. You can still disable the notification separately for each monitor.",
clearEventsMsg: "Are you sure want to delete all events for this monitor?",
clearHeartbeatsMsg: "Are you sure want to delete all heartbeats for this monitor?",
confirmClearStatisticsMsg: "Are you sure you want to delete ALL statistics?",
importHandleDescription: "Choose 'Skip existing' if you want to skip every monitor or notification with the same name. 'Overwrite' will delete every existing monitor and notification.",
confirmImportMsg: "Are you sure to import the backup? Please make sure you've selected the right import option.",
twoFAVerifyLabel: "Please type in your token to verify that 2FA is working",
confirmImportMsg: "Are you sure you want to import the backup? Please verify you've selected the correct import option.",
twoFAVerifyLabel: "Please enter your token to verify 2FA:",
tokenValidSettingsMsg: "Token is valid! You can now save the 2FA settings.",
confirmEnableTwoFAMsg: "Are you sure you want to enable 2FA?",
confirmDisableTwoFAMsg: "Are you sure you want to disable 2FA?",
@ -77,7 +77,7 @@ export default {
"Max. Redirects": "Max. Redirects",
"Accepted Status Codes": "Accepted Status Codes",
"Push URL": "Push URL",
needPushEvery: "You should call this url every {0} seconds.",
needPushEvery: "You should call this URL every {0} seconds.",
pushOptionalParams: "Optional parameters: {0}",
Save: "Save",
Notifications: "Notifications",
@ -135,9 +135,9 @@ export default {
Events: "Events",
Heartbeats: "Heartbeats",
"Auto Get": "Auto Get",
backupDescription: "You can backup all monitors and all notifications into a JSON file.",
backupDescription2: "PS: History and event data is not included.",
backupDescription3: "Sensitive data such as notification tokens is included in the export file, please keep it carefully.",
backupDescription: "You can backup all monitors and notifications into a JSON file.",
backupDescription2: "Note: history and event data is not included.",
backupDescription3: "Sensitive data such as notification tokens are included in the export file; please store export securely.",
alertNoFile: "Please select a file to import.",
alertWrongFileType: "Please select a JSON file.",
"Clear all statistics": "Clear all Statistics",
@ -157,8 +157,8 @@ export default {
"Show URI": "Show URI",
Tags: "Tags",
"Add New below or Select...": "Add New below or Select...",
"Tag with this name already exist.": "Tag with this name already exist.",
"Tag with this value already exist.": "Tag with this value already exist.",
"Tag with this name already exist.": "Tag with this name already exists.",
"Tag with this value already exist.": "Tag with this value already exists.",
color: "color",
"value (optional)": "value (optional)",
Gray: "Gray",
@ -192,14 +192,14 @@ export default {
wayToGetTelegramToken: "You can get a token from {0}.",
"Chat ID": "Chat ID",
supportTelegramChatID: "Support Direct Chat / Group / Channel's Chat ID",
wayToGetTelegramChatID: "You can get your chat id by sending message to the bot and go to this url to view the chat_id:",
wayToGetTelegramChatID: "You can get your chat ID by sending a message to the bot and going to this URL to view the chat_id:",
"YOUR BOT TOKEN HERE": "YOUR BOT TOKEN HERE",
chatIDNotFound: "Chat ID is not found, please send a message to this bot first",
chatIDNotFound: "Chat ID is not found; please send a message to this bot first",
"webhook": "Webhook",
"Post URL": "Post URL",
"Content Type": "Content Type",
webhookJsonDesc: "{0} is good for any modern http servers such as express.js",
webhookFormDataDesc: "{multipart} is good for PHP, you just need to parse the json by {decodeFunction}",
webhookJsonDesc: "{0} is good for any modern HTTP servers such as Express.js",
webhookFormDataDesc: "{multipart} is good for PHP. The JSON will need to be parsed with {decodeFunction}",
"smtp": "Email (SMTP)",
secureOptionNone: "None / STARTTLS (25, 587)",
secureOptionTLS: "TLS (465)",
@ -217,12 +217,12 @@ export default {
"Hello @everyone is...": "Hello {'@'}everyone is...",
"teams": "Microsoft Teams",
"Webhook URL": "Webhook URL",
wayToGetTeamsURL: "You can learn how to create a webhook url {0}.",
wayToGetTeamsURL: "You can learn how to create a webhook URL {0}.",
"signal": "Signal",
"Number": "Number",
"Recipients": "Recipients",
needSignalAPI: "You need to have a signal client with REST API.",
wayToCheckSignalURL: "You can check this url to view how to setup one:",
wayToCheckSignalURL: "You can check this URL to view how to set one up:",
signalImportant: "IMPORTANT: You cannot mix groups and numbers in recipients!",
"gotify": "Gotify",
"Application Token": "Application Token",
@ -232,11 +232,11 @@ export default {
"Icon Emoji": "Icon Emoji",
"Channel Name": "Channel Name",
"Uptime Kuma URL": "Uptime Kuma URL",
aboutWebhooks: "More info about webhooks on: {0}",
aboutChannelName: "Enter the channel name on {0} Channel Name field if you want to bypass the webhook channel. Ex: #other-channel",
aboutKumaURL: "If you leave the Uptime Kuma URL field blank, it will default to the Project Github page.",
aboutWebhooks: "More info about Webhooks on: {0}",
aboutChannelName: "Enter the channel name on {0} Channel Name field if you want to bypass the Webhook channel. Ex: #other-channel",
aboutKumaURL: "If you leave the Uptime Kuma URL field blank, it will default to the Project GitHub page.",
emojiCheatSheet: "Emoji cheat sheet: {0}",
"rocket.chat": "Rocket.chat",
"rocket.chat": "Rocket.Chat",
pushover: "Pushover",
pushy: "Pushy",
octopush: "Octopush",
@ -255,7 +255,7 @@ export default {
pushoverDesc2: "If you want to send notifications to different devices, fill out Device field.",
"SMS Type": "SMS Type",
octopushTypePremium: "Premium (Fast - recommended for alerting)",
octopushTypeLowCost: "Low Cost (Slow, sometimes blocked by operator)",
octopushTypeLowCost: "Low Cost (Slow - sometimes blocked by operator)",
checkPrice: "Check {0} prices:",
octopushLegacyHint: "Do you use the legacy version of Octopush (2011-2020) or the new version?",
"Check octopush prices": "Check octopush prices {0}.",
@ -276,20 +276,20 @@ export default {
"Basic Settings": "Basic Settings",
"User ID": "User ID",
"Messaging API": "Messaging API",
wayToGetLineChannelToken: "First access the {0}, create a provider and channel (Messaging API), then you can get the channel access token and user id from the above mentioned menu items.",
wayToGetLineChannelToken: "First access the {0}, create a provider and channel (Messaging API), then you can get the channel access token and user ID from the above mentioned menu items.",
"Icon URL": "Icon URL",
aboutIconURL: "You can provide a link to a picture in \"Icon URL\" to override the default profile picture. Will not be used if Icon Emoji is set.",
aboutMattermostChannelName: "You can override the default channel that webhook posts to by entering the channel name into \"Channel Name\" field. This needs to be enabled in Mattermost webhook settings. Ex: #other-channel",
aboutMattermostChannelName: "You can override the default channel that the Webhook posts to by entering the channel name into \"Channel Name\" field. This needs to be enabled in the Mattermost Webhook settings. Ex: #other-channel",
"matrix": "Matrix",
promosmsTypeEco: "SMS ECO - cheap but slow and often overloaded. Limited only to Polish recipients.",
promosmsTypeFlash: "SMS FLASH - Message will automatically show on recipient device. Limited only to Polish recipients.",
promosmsTypeFull: "SMS FULL - Premium tier of SMS, You can use Your Sender Name (You need to register name first). Reliable for alerts.",
promosmsTypeFull: "SMS FULL - Premium tier of SMS, You can use your Sender Name (You need to register name first). Reliable for alerts.",
promosmsTypeSpeed: "SMS SPEED - Highest priority in system. Very quick and reliable but costly (about twice of SMS FULL price).",
promosmsPhoneNumber: "Phone number (for Polish recipient You can skip area codes)",
promosmsSMSSender: "SMS Sender Name : Pre-registred name or one of defaults: InfoSMS, SMS Info, MaxSMS, INFO, SMS",
"Feishu WebHookUrl": "Feishu WebHookUrl",
"Feishu WebHookUrl": "Feishu WebHookURL",
matrixHomeserverURL: "Homeserver URL (with http(s):// and optionally port)",
"Internal Room Id": "Internal Room Id",
"Internal Room Id": "Internal Room ID",
matrixDesc1: "You can find the internal room ID by looking in the advanced section of the room settings in your Matrix client. It should look like !QMdRCpUIfLwsfjxye6:home.server.",
matrixDesc2: "It is highly recommended you create a new user and do not use your own Matrix user's access token as it will allow full access to your account and all the rooms you joined. Instead, create a new user and only invite it to the room that you want to receive the notification in. You can get the access token by running {0}",
// End notification form
@ -301,7 +301,9 @@ export default {
BodyInvalidFormat: "The request body is not valid JSON: ",
"Monitor History": "Monitor History:",
clearDataOlderThan: "Keep monitor history data for {0} days.",
PasswordsDoNotMatch: "Passwords do not match.",
records: "records",
"One record": "One record",
"Showing {from} to {to} of {count} records": "Showing {from} to {to} of {count} records",
steamApiKeyDescription: "For monitoring a Steam Game Server you need a Steam Web-API key. You can register your API key here: ",
};

8
src/mixins/socket.js

@ -347,7 +347,7 @@ export default {
let result = {};
let unknown = {
text: "Unknown",
text: this.$t("Unknown"),
color: "secondary",
};
@ -358,17 +358,17 @@ export default {
result[monitorID] = unknown;
} else if (lastHeartBeat.status === 1) {
result[monitorID] = {
text: "Up",
text: this.$t("Up"),
color: "primary",
};
} else if (lastHeartBeat.status === 0) {
result[monitorID] = {
text: "Down",
text: this.$t("Down"),
color: "danger",
};
} else if (lastHeartBeat.status === 2) {
result[monitorID] = {
text: "Pending",
text: this.$t("Pending"),
color: "warning",
};
} else {

2
src/pages/Details.vue

@ -41,7 +41,7 @@
<span class="word">{{ $t("checkEverySecond", [ monitor.interval ]) }}</span>
</div>
<div class="col-md-4 text-center">
<span class="badge rounded-pill" :class=" 'bg-' + status.color " style="font-size: 30px;">{{ $t(status.text) }}</span>
<span class="badge rounded-pill" :class=" 'bg-' + status.color " style="font-size: 30px;">{{ status.text }}</span>
</div>
</div>
</div>

15
src/pages/EditMonitor.vue

@ -29,6 +29,9 @@
<option value="push">
Push
</option>
<option value="steam">
Steam Game Server
</option>
</select>
</div>
@ -64,18 +67,20 @@
</div>
<!-- Hostname -->
<!-- TCP Port / Ping / DNS only -->
<div v-if="monitor.type === 'port' || monitor.type === 'ping' || monitor.type === 'dns' " class="my-3">
<!-- TCP Port / Ping / DNS / Steam only -->
<div v-if="monitor.type === 'port' || monitor.type === 'ping' || monitor.type === 'dns' || monitor.type === 'steam'" class="my-3">
<label for="hostname" class="form-label">{{ $t("Hostname") }}</label>
<input id="hostname" v-model="monitor.hostname" type="text" class="form-control" :pattern="`${ipRegexPattern}|${hostnameRegexPattern}`" required>
</div>
<!-- For TCP Port Type -->
<div v-if="monitor.type === 'port' " class="my-3">
<!-- Port -->
<!-- For TCP Port / Steam Type -->
<div v-if="monitor.type === 'port' || monitor.type === 'steam'" class="my-3">
<label for="port" class="form-label">{{ $t("Port") }}</label>
<input id="port" v-model="monitor.port" type="number" class="form-control" required min="0" max="65535" step="1">
</div>
<!-- DNS Resolver Server -->
<!-- For DNS Type -->
<template v-if="monitor.type === 'dns'">
<div class="my-3">
@ -467,7 +472,7 @@ export default {
return;
}
// Beautiful the JSON format
// Beautify the JSON format
if (this.monitor.body) {
this.monitor.body = JSON.stringify(JSON.parse(this.monitor.body), null, 4);
}

10
src/pages/Settings.vue

@ -119,6 +119,15 @@
</div>
</div>
<!-- Steam API Key -->
<div class="mb-4">
<label class="form-label" for="steamAPIKey">{{ $t("Steam API Key") }}</label>
<input id="steamAPIKey" v-model="settings.steamAPIKey" class="form-control" name="steamAPIKey">
<div class="form-text">
{{ $t("steamApiKeyDescription") }}<a href="https://steamcommunity.com/dev" target="_blank">https://steamcommunity.com/dev</a>
</div>
</div>
<!-- Monitor History -->
<div class="mb-4">
<h4 class="mt-4">{{ $t("Monitor History") }}</h4>
@ -128,6 +137,7 @@
</div>
</div>
<!-- Save Button -->
<div>
<button class="btn btn-primary" type="submit">
{{ $t("Save") }}

2
src/pages/Setup.vue

@ -75,7 +75,7 @@ export default {
this.processing = true;
if (this.password !== this.repeatPassword) {
toast.error("Repeat password do not match.");
toast.error(this.$t("PasswordsDoNotMatch"));
this.processing = false;
return;
}

57
src/util.js

@ -7,7 +7,7 @@
// Backend uses the compiled file util.js
// Frontend uses util.ts
Object.defineProperty(exports, "__esModule", { value: true });
exports.getMonitorRelativeURL = exports.genSecret = exports.getRandomInt = exports.getRandomArbitrary = exports.TimeLogger = exports.polyfill = exports.debug = exports.ucfirst = exports.sleep = exports.flipStatus = exports.STATUS_PAGE_PARTIAL_DOWN = exports.STATUS_PAGE_ALL_UP = exports.STATUS_PAGE_ALL_DOWN = exports.PENDING = exports.UP = exports.DOWN = exports.appName = exports.isDev = void 0;
exports.getMonitorRelativeURL = exports.genSecret = exports.getCryptoRandomInt = exports.getRandomInt = exports.getRandomArbitrary = exports.TimeLogger = exports.polyfill = exports.debug = exports.ucfirst = exports.sleep = exports.flipStatus = exports.STATUS_PAGE_PARTIAL_DOWN = exports.STATUS_PAGE_ALL_UP = exports.STATUS_PAGE_ALL_DOWN = exports.PENDING = exports.UP = exports.DOWN = exports.appName = exports.isDev = void 0;
const _dayjs = require("dayjs");
const dayjs = _dayjs;
exports.isDev = process.env.NODE_ENV === "development";
@ -102,12 +102,61 @@ function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
exports.getRandomInt = getRandomInt;
/**
* Returns either the NodeJS crypto.randomBytes() function or its
* browser equivalent implemented via window.crypto.getRandomValues()
*/
let getRandomBytes = ((typeof window !== 'undefined' && window.crypto)
// Browsers
? function () {
return (numBytes) => {
let randomBytes = new Uint8Array(numBytes);
for (let i = 0; i < numBytes; i += 65536) {
window.crypto.getRandomValues(randomBytes.subarray(i, i + Math.min(numBytes - i, 65536)));
}
return randomBytes;
};
}
// Node
: function () {
return require("crypto").randomBytes;
})();
function getCryptoRandomInt(min, max) {
// synchronous version of: https://github.com/joepie91/node-random-number-csprng
const range = max - min;
if (range >= Math.pow(2, 32))
console.log("Warning! Range is too large.");
let tmpRange = range;
let bitsNeeded = 0;
let bytesNeeded = 0;
let mask = 1;
while (tmpRange > 0) {
if (bitsNeeded % 8 === 0)
bytesNeeded += 1;
bitsNeeded += 1;
mask = mask << 1 | 1;
tmpRange = tmpRange >>> 1;
}
const randomBytes = getRandomBytes(bytesNeeded);
let randomValue = 0;
for (let i = 0; i < bytesNeeded; i++) {
randomValue |= randomBytes[i] << 8 * i;
}
randomValue = randomValue & mask;
if (randomValue <= range) {
return min + randomValue;
}
else {
return getCryptoRandomInt(min, max);
}
}
exports.getCryptoRandomInt = getCryptoRandomInt;
function genSecret(length = 64) {
let secret = "";
let chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
let charsLength = chars.length;
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
const charsLength = chars.length;
for (let i = 0; i < length; i++) {
secret += chars.charAt(Math.floor(Math.random() * charsLength));
secret += chars.charAt(getCryptoRandomInt(0, charsLength - 1));
}
return secret;
}

66
src/util.ts

@ -114,12 +114,72 @@ export function getRandomInt(min: number, max: number) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
/**
* Returns either the NodeJS crypto.randomBytes() function or its
* browser equivalent implemented via window.crypto.getRandomValues()
*/
let getRandomBytes = (
(typeof window !== 'undefined' && window.crypto)
// Browsers
? function () {
return (numBytes: number) => {
let randomBytes = new Uint8Array(numBytes);
for (let i = 0; i < numBytes; i += 65536) {
window.crypto.getRandomValues(randomBytes.subarray(i, i + Math.min(numBytes - i, 65536)));
}
return randomBytes;
};
}
// Node
: function() {
return require("crypto").randomBytes;
}
)();
export function getCryptoRandomInt(min: number, max: number):number {
// synchronous version of: https://github.com/joepie91/node-random-number-csprng
const range = max - min
if (range >= Math.pow(2, 32))
console.log("Warning! Range is too large.")
let tmpRange = range
let bitsNeeded = 0
let bytesNeeded = 0
let mask = 1
while (tmpRange > 0) {
if (bitsNeeded % 8 === 0) bytesNeeded += 1
bitsNeeded += 1
mask = mask << 1 | 1
tmpRange = tmpRange >>> 1
}
const randomBytes = getRandomBytes(bytesNeeded)
let randomValue = 0
for (let i = 0; i < bytesNeeded; i++) {
randomValue |= randomBytes[i] << 8 * i
}
randomValue = randomValue & mask;
if (randomValue <= range) {
return min + randomValue
} else {
return getCryptoRandomInt(min, max)
}
}
export function genSecret(length = 64) {
let secret = "";
let chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
let charsLength = chars.length;
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
const charsLength = chars.length;
for ( let i = 0; i < length; i++ ) {
secret += chars.charAt(Math.floor(Math.random() * charsLength));
secret += chars.charAt(getCryptoRandomInt(0, charsLength - 1));
}
return secret;
}

Loading…
Cancel
Save