committed by
							
								
								GitHub
							
						
					
				
				 208 changed files with 38779 additions and 9777 deletions
			
			
		@ -0,0 +1,12 @@ | 
				
			|||
# These are supported funding model platforms | 
				
			|||
 | 
				
			|||
github: louislam # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] | 
				
			|||
#patreon: # Replace with a single Patreon username | 
				
			|||
open_collective: uptime-kuma # Replace with a single Open Collective username | 
				
			|||
#ko_fi: # Replace with a single Ko-fi username | 
				
			|||
#tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel | 
				
			|||
#community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry | 
				
			|||
#liberapay: # Replace with a single Liberapay username | 
				
			|||
#issuehunt: # Replace with a single IssueHunt username | 
				
			|||
#otechie: # Replace with a single Otechie username | 
				
			|||
#custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] | 
				
			|||
@ -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 }} | 
				
			|||
@ -1,3 +1,9 @@ | 
				
			|||
{ | 
				
			|||
  "extends": "stylelint-config-recommended", | 
				
			|||
    "extends": "stylelint-config-standard", | 
				
			|||
    "rules": { | 
				
			|||
        "indentation": 4, | 
				
			|||
        "no-descending-specificity": null, | 
				
			|||
        "selector-list-comma-newline-after": null, | 
				
			|||
        "declaration-empty-line-before": null | 
				
			|||
    } | 
				
			|||
} | 
				
			|||
 | 
				
			|||
@ -0,0 +1 @@ | 
				
			|||
git.kuma.pet | 
				
			|||
@ -0,0 +1,31 @@ | 
				
			|||
# Security Policy | 
				
			|||
 | 
				
			|||
## Supported Versions | 
				
			|||
 | 
				
			|||
Use this section to tell people about which versions of your project are | 
				
			|||
currently being supported with security updates. | 
				
			|||
 | 
				
			|||
### Uptime Kuma Versions | 
				
			|||
 | 
				
			|||
| Version | Supported          | | 
				
			|||
| ------- | ------------------ | | 
				
			|||
| 1.7.X  | :white_check_mark: | | 
				
			|||
| < 1.7  | ❌ | | 
				
			|||
 | 
				
			|||
### Upgradable Docker Tags | 
				
			|||
 | 
				
			|||
| Tag | Supported          | | 
				
			|||
| ------- | ------------------ | | 
				
			|||
| 1 | :white_check_mark: | | 
				
			|||
| 1-debian | :white_check_mark: | | 
				
			|||
| 1-alpine | :white_check_mark: | | 
				
			|||
| latest | :white_check_mark: | | 
				
			|||
| debian | :white_check_mark: | | 
				
			|||
| alpine | :white_check_mark: | | 
				
			|||
| All other tags  | ❌ | | 
				
			|||
 | 
				
			|||
## Reporting a Vulnerability | 
				
			|||
 | 
				
			|||
Please report security issues to uptime@kuma.pet. | 
				
			|||
 | 
				
			|||
Do not use the issue tracker or discuss it in the public as it will cause more damage. | 
				
			|||
@ -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,10 @@ | 
				
			|||
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db. | 
				
			|||
BEGIN TRANSACTION; | 
				
			|||
 | 
				
			|||
ALTER TABLE user | 
				
			|||
    ADD twofa_secret VARCHAR(64); | 
				
			|||
 | 
				
			|||
ALTER TABLE user | 
				
			|||
    ADD twofa_status BOOLEAN default 0 NOT 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 retry_interval INTEGER default 0 not null; | 
				
			|||
 | 
				
			|||
COMMIT; | 
				
			|||
@ -0,0 +1,30 @@ | 
				
			|||
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db. | 
				
			|||
BEGIN TRANSACTION; | 
				
			|||
 | 
				
			|||
create table `group` | 
				
			|||
( | 
				
			|||
    id           INTEGER      not null | 
				
			|||
        constraint group_pk | 
				
			|||
            primary key autoincrement, | 
				
			|||
    name         VARCHAR(255) not null, | 
				
			|||
    created_date DATETIME              default (DATETIME('now')) not null, | 
				
			|||
    public       BOOLEAN               default 0 not null, | 
				
			|||
    active       BOOLEAN               default 1 not null, | 
				
			|||
    weight       BOOLEAN      NOT NULL DEFAULT 1000 | 
				
			|||
); | 
				
			|||
 | 
				
			|||
CREATE TABLE [monitor_group] | 
				
			|||
( | 
				
			|||
    [id]         INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, | 
				
			|||
    [monitor_id] INTEGER                           NOT NULL REFERENCES [monitor] ([id]) ON DELETE CASCADE ON UPDATE CASCADE, | 
				
			|||
    [group_id]   INTEGER                           NOT NULL REFERENCES [group] ([id]) ON DELETE CASCADE ON UPDATE CASCADE, | 
				
			|||
    weight BOOLEAN NOT NULL DEFAULT 1000 | 
				
			|||
); | 
				
			|||
 | 
				
			|||
CREATE INDEX [fk] | 
				
			|||
    ON [monitor_group] ( | 
				
			|||
                        [monitor_id], | 
				
			|||
                        [group_id]); | 
				
			|||
 | 
				
			|||
 | 
				
			|||
COMMIT; | 
				
			|||
@ -0,0 +1,10 @@ | 
				
			|||
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db. | 
				
			|||
BEGIN TRANSACTION; | 
				
			|||
 | 
				
			|||
-- For sendHeartbeatList | 
				
			|||
CREATE INDEX monitor_time_index ON heartbeat (monitor_id, time); | 
				
			|||
 | 
				
			|||
-- For sendImportantHeartbeatList | 
				
			|||
CREATE INDEX monitor_important_time_index ON heartbeat (monitor_id, important,time); | 
				
			|||
 | 
				
			|||
COMMIT; | 
				
			|||
@ -0,0 +1,18 @@ | 
				
			|||
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db. | 
				
			|||
BEGIN TRANSACTION; | 
				
			|||
 | 
				
			|||
create table incident | 
				
			|||
( | 
				
			|||
    id INTEGER not null | 
				
			|||
        constraint incident_pk | 
				
			|||
            primary key autoincrement, | 
				
			|||
    title VARCHAR(255) not null, | 
				
			|||
    content TEXT not null, | 
				
			|||
    style VARCHAR(30) default 'warning' not null, | 
				
			|||
    created_date DATETIME default (DATETIME('now')) not null, | 
				
			|||
    last_updated_date DATETIME, | 
				
			|||
    pin BOOLEAN default 1 not null, | 
				
			|||
    active BOOLEAN default 1 not 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,22 @@ | 
				
			|||
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db. | 
				
			|||
BEGIN TRANSACTION; | 
				
			|||
 | 
				
			|||
-- Generated by Intellij IDEA | 
				
			|||
create table setting_dg_tmp | 
				
			|||
( | 
				
			|||
    id INTEGER | 
				
			|||
        primary key autoincrement, | 
				
			|||
    key VARCHAR(200) not null | 
				
			|||
        unique, | 
				
			|||
    value TEXT, | 
				
			|||
    type VARCHAR(20) | 
				
			|||
); | 
				
			|||
 | 
				
			|||
insert into setting_dg_tmp(id, key, value, type) select id, key, value, type from setting; | 
				
			|||
 | 
				
			|||
drop table setting; | 
				
			|||
 | 
				
			|||
alter table setting_dg_tmp rename to setting; | 
				
			|||
 | 
				
			|||
 | 
				
			|||
COMMIT; | 
				
			|||
@ -0,0 +1,19 @@ | 
				
			|||
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db. | 
				
			|||
CREATE TABLE tag ( | 
				
			|||
	id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, | 
				
			|||
	name VARCHAR(255) NOT NULL, | 
				
			|||
    color VARCHAR(255) NOT NULL, | 
				
			|||
	created_date DATETIME DEFAULT (DATETIME('now')) NOT NULL | 
				
			|||
); | 
				
			|||
 | 
				
			|||
CREATE TABLE monitor_tag ( | 
				
			|||
	id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, | 
				
			|||
	monitor_id INTEGER NOT NULL, | 
				
			|||
	tag_id INTEGER NOT NULL, | 
				
			|||
	value TEXT, | 
				
			|||
	CONSTRAINT FK_tag FOREIGN KEY (tag_id) REFERENCES tag(id) ON DELETE CASCADE ON UPDATE CASCADE, | 
				
			|||
	CONSTRAINT FK_monitor FOREIGN KEY (monitor_id) REFERENCES monitor(id) ON DELETE CASCADE ON UPDATE CASCADE | 
				
			|||
); | 
				
			|||
 | 
				
			|||
CREATE INDEX monitor_tag_monitor_id_index ON monitor_tag (monitor_id); | 
				
			|||
CREATE INDEX monitor_tag_tag_id_index ON monitor_tag (tag_id); | 
				
			|||
@ -0,0 +1,10 @@ | 
				
			|||
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db. | 
				
			|||
BEGIN TRANSACTION; | 
				
			|||
 | 
				
			|||
ALTER TABLE monitor | 
				
			|||
	ADD dns_resolve_type VARCHAR(5); | 
				
			|||
 | 
				
			|||
ALTER TABLE monitor | 
				
			|||
	ADD dns_resolve_server VARCHAR(255); | 
				
			|||
 | 
				
			|||
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 dns_last_result VARCHAR(255); | 
				
			|||
 | 
				
			|||
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 notification | 
				
			|||
    ADD is_default BOOLEAN default 0 NOT 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 | 
				
			|||
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,28 +0,0 @@ | 
				
			|||
# DON'T UPDATE TO alpine3.13, 1.14, see #41. | 
				
			|||
FROM node:14-alpine3.12 AS release | 
				
			|||
WORKDIR /app | 
				
			|||
 | 
				
			|||
# split the sqlite install here, so that it can caches the arm prebuilt | 
				
			|||
RUN apk add --no-cache --virtual .build-deps make g++ python3 python3-dev && \ | 
				
			|||
            ln -s /usr/bin/python3 /usr/bin/python && \ | 
				
			|||
            npm install @louislam/sqlite3@5.0.3 bcrypt@5.0.1 && \ | 
				
			|||
            apk del .build-deps && \ | 
				
			|||
            rm -f /usr/bin/python | 
				
			|||
 | 
				
			|||
# Touching above code may causes sqlite3 re-compile again, painful slow. | 
				
			|||
 | 
				
			|||
# Install apprise | 
				
			|||
RUN apk add --no-cache python3 py3-cryptography py3-pip py3-six py3-yaml py3-click py3-markdown py3-requests py3-requests-oauthlib | 
				
			|||
RUN pip3 --no-cache-dir install apprise && \ | 
				
			|||
            rm -rf /root/.cache | 
				
			|||
 | 
				
			|||
COPY . . | 
				
			|||
RUN npm install && npm run build && npm prune | 
				
			|||
 | 
				
			|||
EXPOSE 3001 | 
				
			|||
VOLUME ["/app/data"] | 
				
			|||
HEALTHCHECK --interval=60s --timeout=30s --start-period=300s CMD node extra/healthcheck.js | 
				
			|||
CMD ["npm", "run", "start-server"] | 
				
			|||
 | 
				
			|||
FROM release AS nightly | 
				
			|||
RUN npm run mark-as-nightly | 
				
			|||
@ -0,0 +1,6 @@ | 
				
			|||
module.exports = { | 
				
			|||
  apps: [{ | 
				
			|||
    name: "uptime-kuma", | 
				
			|||
    script: "./server/server.js", | 
				
			|||
  }] | 
				
			|||
} | 
				
			|||
@ -1 +1,2 @@ | 
				
			|||
# Must enable File Sharing in Docker Desktop | 
				
			|||
docker run -it --rm -v ${pwd}:/app louislam/batsh /usr/bin/batsh bash --output ./install.sh ./extra/install.batsh | 
				
			|||
@ -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,21 @@ | 
				
			|||
#!/usr/bin/env sh | 
				
			|||
 | 
				
			|||
# set -e Exit the script if an error happens | 
				
			|||
set -e | 
				
			|||
PUID=${PUID=0} | 
				
			|||
PGID=${PGID=0} | 
				
			|||
 | 
				
			|||
files_ownership () { | 
				
			|||
    # -h Changes the ownership of an encountered symbolic link and not that of the file or directory pointed to by the symbolic link. | 
				
			|||
    # -R Recursively descends the specified directories | 
				
			|||
    # -c Like verbose but report only when a change is made | 
				
			|||
    chown -hRc "$PUID":"$PGID" /app/data | 
				
			|||
} | 
				
			|||
 | 
				
			|||
echo "==> Performing startup jobs and maintenance tasks" | 
				
			|||
files_ownership | 
				
			|||
 | 
				
			|||
echo "==> Starting application with user $PUID group $PGID" | 
				
			|||
 | 
				
			|||
# --clear-groups Clear supplementary groups. | 
				
			|||
exec setpriv --reuid "$PUID" --regid "$PGID" --clear-groups "$@" | 
				
			|||
@ -1,19 +1,34 @@ | 
				
			|||
let http = require("http"); | 
				
			|||
/* | 
				
			|||
 * This script should be run after a period of time (180s), because the server may need some time to prepare. | 
				
			|||
 */ | 
				
			|||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"; | 
				
			|||
 | 
				
			|||
let client; | 
				
			|||
 | 
				
			|||
if (process.env.SSL_KEY && process.env.SSL_CERT) { | 
				
			|||
    client = require("https"); | 
				
			|||
} else { | 
				
			|||
    client = require("http"); | 
				
			|||
} | 
				
			|||
 | 
				
			|||
let options = { | 
				
			|||
    host: "localhost", | 
				
			|||
    port: "3001", | 
				
			|||
    timeout: 2000, | 
				
			|||
    host: process.env.HOST || "127.0.0.1", | 
				
			|||
    port: parseInt(process.env.PORT) || 3001, | 
				
			|||
    timeout: 28 * 1000, | 
				
			|||
}; | 
				
			|||
let request = http.request(options, (res) => { | 
				
			|||
    console.log(`STATUS: ${res.statusCode}`); | 
				
			|||
    if (res.statusCode == 200) { | 
				
			|||
 | 
				
			|||
let request = client.request(options, (res) => { | 
				
			|||
    console.log(`Health Check OK [Res Code: ${res.statusCode}]`); | 
				
			|||
    if (res.statusCode === 200) { | 
				
			|||
        process.exit(0); | 
				
			|||
    } else { | 
				
			|||
        process.exit(1); | 
				
			|||
    } | 
				
			|||
}); | 
				
			|||
 | 
				
			|||
request.on("error", function (err) { | 
				
			|||
    console.log("ERROR"); | 
				
			|||
    console.error("Health Check ERROR"); | 
				
			|||
    process.exit(1); | 
				
			|||
}); | 
				
			|||
 | 
				
			|||
request.end(); | 
				
			|||
 | 
				
			|||
@ -0,0 +1,245 @@ | 
				
			|||
// install.sh is generated by ./extra/install.batsh, do not modify it directly. | 
				
			|||
// "npm run compile-install-script" to compile install.sh | 
				
			|||
// The command is working on Windows PowerShell and Docker for Windows only. | 
				
			|||
 | 
				
			|||
 | 
				
			|||
// curl -o kuma_install.sh https://raw.githubusercontent.com/louislam/uptime-kuma/master/install.sh && sudo bash kuma_install.sh | 
				
			|||
println("====================="); | 
				
			|||
println("Uptime Kuma Installer"); | 
				
			|||
println("====================="); | 
				
			|||
println("Supported OS: CentOS 7/8, Ubuntu >= 16.04 and Debian"); | 
				
			|||
println("---------------------------------------"); | 
				
			|||
println("This script is designed for Linux and basic usage."); | 
				
			|||
println("For advanced usage, please go to https://github.com/louislam/uptime-kuma/wiki/Installation"); | 
				
			|||
println("---------------------------------------"); | 
				
			|||
println(""); | 
				
			|||
println("Local - Install Uptime Kuma in your current machine with git, Node.js 14 and pm2"); | 
				
			|||
println("Docker - Install Uptime Kuma Docker container"); | 
				
			|||
println(""); | 
				
			|||
 | 
				
			|||
if ("$1" != "") { | 
				
			|||
    type = "$1"; | 
				
			|||
} else { | 
				
			|||
    call("read", "-p", "Which installation method do you prefer? [DOCKER/local]: ", "type"); | 
				
			|||
} | 
				
			|||
 | 
				
			|||
defaultPort = "3001"; | 
				
			|||
 | 
				
			|||
function checkNode() { | 
				
			|||
    bash("nodeVersion=$(node -e 'console.log(process.versions.node.split(`.`)[0])')"); | 
				
			|||
    println("Node Version: " ++ nodeVersion); | 
				
			|||
 | 
				
			|||
    if (nodeVersion < "12") { | 
				
			|||
        println("Error: Required Node.js 14"); | 
				
			|||
        call("exit", "1"); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    if (nodeVersion == "12") { | 
				
			|||
        println("Warning: NodeJS " ++ nodeVersion ++ " is not tested."); | 
				
			|||
    } | 
				
			|||
} | 
				
			|||
 | 
				
			|||
function deb() { | 
				
			|||
    bash("nodeCheck=$(node -v)"); | 
				
			|||
    bash("apt --yes update"); | 
				
			|||
 | 
				
			|||
    if (nodeCheck != "") { | 
				
			|||
        checkNode(); | 
				
			|||
    } else { | 
				
			|||
 | 
				
			|||
        // Old nodejs binary name is "nodejs" | 
				
			|||
        bash("check=$(nodejs --version)"); | 
				
			|||
        if (check != "") { | 
				
			|||
            println("Error: 'node' command is not found, but 'nodejs' command is found. Your NodeJS should be too old."); | 
				
			|||
            bash("exit 1"); | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        bash("curlCheck=$(curl --version)"); | 
				
			|||
        if (curlCheck == "") { | 
				
			|||
            println("Installing Curl"); | 
				
			|||
            bash("apt --yes install curl"); | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        println("Installing Node.js 14"); | 
				
			|||
        bash("curl -sL https://deb.nodesource.com/setup_14.x | bash - > log.txt"); | 
				
			|||
        bash("apt --yes install nodejs"); | 
				
			|||
        bash("node -v"); | 
				
			|||
 | 
				
			|||
        bash("nodeCheckAgain=$(node -v)"); | 
				
			|||
 | 
				
			|||
        if (nodeCheckAgain == "") { | 
				
			|||
            println("Error during Node.js installation"); | 
				
			|||
            bash("exit 1"); | 
				
			|||
        } | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    bash("check=$(git --version)"); | 
				
			|||
    if (check == "") { | 
				
			|||
        println("Installing Git"); | 
				
			|||
        bash("apt --yes install git"); | 
				
			|||
    } | 
				
			|||
} | 
				
			|||
 | 
				
			|||
if (type == "local") { | 
				
			|||
    defaultInstallPath = "/opt/uptime-kuma"; | 
				
			|||
 | 
				
			|||
    if (exists("/etc/redhat-release")) { | 
				
			|||
        os = call("cat", "/etc/redhat-release"); | 
				
			|||
        distribution = "rhel"; | 
				
			|||
 | 
				
			|||
    } else if (exists("/etc/issue")) { | 
				
			|||
        bash("os=$(head -n1 /etc/issue | cut -f 1 -d ' ')"); | 
				
			|||
        if (os == "Ubuntu") { | 
				
			|||
            distribution = "ubuntu"; | 
				
			|||
        } | 
				
			|||
        if (os == "Debian") { | 
				
			|||
            distribution = "debian"; | 
				
			|||
        } | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    bash("arch=$(uname -i)"); | 
				
			|||
 | 
				
			|||
    println("Your OS: " ++ os); | 
				
			|||
    println("Distribution: " ++ distribution); | 
				
			|||
    println("Arch: " ++ arch); | 
				
			|||
 | 
				
			|||
    if ("$3" != "") { | 
				
			|||
        port = "$3"; | 
				
			|||
    } else { | 
				
			|||
        call("read", "-p", "Listening Port [$defaultPort]: ", "port"); | 
				
			|||
 | 
				
			|||
        if (port == "") { | 
				
			|||
            port = defaultPort; | 
				
			|||
        } | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    if ("$2" != "") { | 
				
			|||
        installPath = "$2"; | 
				
			|||
    } else { | 
				
			|||
        call("read", "-p", "Installation Path [$defaultInstallPath]: ", "installPath"); | 
				
			|||
 | 
				
			|||
        if (installPath == "") { | 
				
			|||
            installPath = defaultInstallPath; | 
				
			|||
        } | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    // CentOS | 
				
			|||
    if (distribution == "rhel") { | 
				
			|||
        bash("nodeCheck=$(node -v)"); | 
				
			|||
 | 
				
			|||
        if (nodeCheck != "") { | 
				
			|||
            checkNode(); | 
				
			|||
        } else { | 
				
			|||
 | 
				
			|||
            bash("curlCheck=$(curl --version)"); | 
				
			|||
            if (curlCheck == "") { | 
				
			|||
                println("Installing Curl"); | 
				
			|||
                bash("yum -y -q install curl"); | 
				
			|||
            } | 
				
			|||
 | 
				
			|||
            println("Installing Node.js 14"); | 
				
			|||
            bash("curl -sL https://rpm.nodesource.com/setup_14.x | bash - > log.txt"); | 
				
			|||
            bash("yum install -y -q nodejs"); | 
				
			|||
            bash("node -v"); | 
				
			|||
 | 
				
			|||
            bash("nodeCheckAgain=$(node -v)"); | 
				
			|||
 | 
				
			|||
            if (nodeCheckAgain == "") { | 
				
			|||
                println("Error during Node.js installation"); | 
				
			|||
                bash("exit 1"); | 
				
			|||
            } | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        bash("check=$(git --version)"); | 
				
			|||
        if (check == "") { | 
				
			|||
            println("Installing Git"); | 
				
			|||
            bash("yum -y -q install git"); | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
    // Ubuntu | 
				
			|||
    } else if (distribution == "ubuntu") { | 
				
			|||
        deb(); | 
				
			|||
 | 
				
			|||
    // Debian | 
				
			|||
    } else if (distribution == "debian") { | 
				
			|||
        deb(); | 
				
			|||
 | 
				
			|||
    } else { | 
				
			|||
        // Unknown distribution | 
				
			|||
        error = 0; | 
				
			|||
 | 
				
			|||
        bash("check=$(git --version)"); | 
				
			|||
        if (check == "") { | 
				
			|||
            error = 1; | 
				
			|||
            println("Error: git is missing"); | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        bash("check=$(node -v)"); | 
				
			|||
        if (check == "") { | 
				
			|||
            error = 1; | 
				
			|||
            println("Error: node is missing"); | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        if (error > 0) { | 
				
			|||
            println("Please install above missing software"); | 
				
			|||
            bash("exit 1"); | 
				
			|||
        } | 
				
			|||
   } | 
				
			|||
 | 
				
			|||
   bash("check=$(pm2 --version)"); | 
				
			|||
   if (check == "") { | 
				
			|||
       println("Installing PM2"); | 
				
			|||
       bash("npm install pm2 -g"); | 
				
			|||
       bash("pm2 startup"); | 
				
			|||
   } | 
				
			|||
 | 
				
			|||
   bash("mkdir -p $installPath"); | 
				
			|||
   bash("cd $installPath"); | 
				
			|||
   bash("git clone https://github.com/louislam/uptime-kuma.git ."); | 
				
			|||
   bash("npm run setup"); | 
				
			|||
 | 
				
			|||
   bash("pm2 start server/server.js --name uptime-kuma -- --port=$port"); | 
				
			|||
 | 
				
			|||
} else { | 
				
			|||
    defaultVolume = "uptime-kuma"; | 
				
			|||
 | 
				
			|||
    bash("check=$(docker -v)"); | 
				
			|||
    if (check == "") { | 
				
			|||
        println("Error: docker is not found!"); | 
				
			|||
        bash("exit 1"); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    bash("check=$(docker info)"); | 
				
			|||
 | 
				
			|||
    bash("if [[ \"$check\" == *\"Is the docker daemon running\"* ]]; then | 
				
			|||
      \"echo\" \"Error: docker is not running\" | 
				
			|||
      \"exit\" \"1\" | 
				
			|||
    fi"); | 
				
			|||
 | 
				
			|||
    if ("$3" != "") { | 
				
			|||
        port = "$3"; | 
				
			|||
    } else { | 
				
			|||
        call("read", "-p", "Expose Port [$defaultPort]: ", "port"); | 
				
			|||
 | 
				
			|||
        if (port == "") { | 
				
			|||
            port = defaultPort; | 
				
			|||
        } | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    if ("$2" != "") { | 
				
			|||
        volume = "$2"; | 
				
			|||
    } else { | 
				
			|||
        call("read", "-p", "Volume Name [$defaultVolume]: ", "volume"); | 
				
			|||
 | 
				
			|||
        if (volume == "") { | 
				
			|||
            volume = defaultVolume; | 
				
			|||
        } | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    println("Port: $port"); | 
				
			|||
    println("Volume: $volume"); | 
				
			|||
    bash("docker volume create $volume"); | 
				
			|||
    bash("docker run -d --restart=always -p $port:3001 -v $volume:/app/data --name uptime-kuma louislam/uptime-kuma:1"); | 
				
			|||
} | 
				
			|||
 | 
				
			|||
println("http://localhost:$port"); | 
				
			|||
@ -0,0 +1,144 @@ | 
				
			|||
/* | 
				
			|||
 * Simple DNS Server | 
				
			|||
 * For testing DNS monitoring type, dev only | 
				
			|||
 */ | 
				
			|||
const dns2 = require("dns2"); | 
				
			|||
 | 
				
			|||
const { Packet } = dns2; | 
				
			|||
 | 
				
			|||
const server = dns2.createServer({ | 
				
			|||
    udp: true | 
				
			|||
}); | 
				
			|||
 | 
				
			|||
server.on("request", (request, send, rinfo) => { | 
				
			|||
    for (let question of request.questions) { | 
				
			|||
        console.log(question.name, type(question.type), question.class); | 
				
			|||
 | 
				
			|||
        const response = Packet.createResponseFromRequest(request); | 
				
			|||
 | 
				
			|||
        if (question.name === "existing.com") { | 
				
			|||
 | 
				
			|||
            if (question.type === Packet.TYPE.A) { | 
				
			|||
                response.answers.push({ | 
				
			|||
                    name: question.name, | 
				
			|||
                    type: question.type, | 
				
			|||
                    class: question.class, | 
				
			|||
                    ttl: 300, | 
				
			|||
                    address: "1.2.3.4" | 
				
			|||
                }); | 
				
			|||
            } if (question.type === Packet.TYPE.AAAA) { | 
				
			|||
                response.answers.push({ | 
				
			|||
                    name: question.name, | 
				
			|||
                    type: question.type, | 
				
			|||
                    class: question.class, | 
				
			|||
                    ttl: 300, | 
				
			|||
                    address: "fe80::::1234:5678:abcd:ef00", | 
				
			|||
                }); | 
				
			|||
            } else if (question.type === Packet.TYPE.CNAME) { | 
				
			|||
                response.answers.push({ | 
				
			|||
                    name: question.name, | 
				
			|||
                    type: question.type, | 
				
			|||
                    class: question.class, | 
				
			|||
                    ttl: 300, | 
				
			|||
                    domain: "cname1.existing.com", | 
				
			|||
                }); | 
				
			|||
            } else if (question.type === Packet.TYPE.MX) { | 
				
			|||
                response.answers.push({ | 
				
			|||
                    name: question.name, | 
				
			|||
                    type: question.type, | 
				
			|||
                    class: question.class, | 
				
			|||
                    ttl: 300, | 
				
			|||
                    exchange: "mx1.existing.com", | 
				
			|||
                    priority: 5 | 
				
			|||
                }); | 
				
			|||
            } else if (question.type === Packet.TYPE.NS) { | 
				
			|||
                response.answers.push({ | 
				
			|||
                    name: question.name, | 
				
			|||
                    type: question.type, | 
				
			|||
                    class: question.class, | 
				
			|||
                    ttl: 300, | 
				
			|||
                    ns: "ns1.existing.com", | 
				
			|||
                }); | 
				
			|||
            } else if (question.type === Packet.TYPE.SOA) { | 
				
			|||
                response.answers.push({ | 
				
			|||
                    name: question.name, | 
				
			|||
                    type: question.type, | 
				
			|||
                    class: question.class, | 
				
			|||
                    ttl: 300, | 
				
			|||
                    primary: "existing.com", | 
				
			|||
                    admin: "admin@existing.com", | 
				
			|||
                    serial: 2021082701, | 
				
			|||
                    refresh: 300, | 
				
			|||
                    retry: 3, | 
				
			|||
                    expiration: 10, | 
				
			|||
                    minimum: 10, | 
				
			|||
                }); | 
				
			|||
            } else if (question.type === Packet.TYPE.SRV) { | 
				
			|||
                response.answers.push({ | 
				
			|||
                    name: question.name, | 
				
			|||
                    type: question.type, | 
				
			|||
                    class: question.class, | 
				
			|||
                    ttl: 300, | 
				
			|||
                    priority: 5, | 
				
			|||
                    weight: 5, | 
				
			|||
                    port: 8080, | 
				
			|||
                    target: "srv1.existing.com", | 
				
			|||
                }); | 
				
			|||
            } else if (question.type === Packet.TYPE.TXT) { | 
				
			|||
                response.answers.push({ | 
				
			|||
                    name: question.name, | 
				
			|||
                    type: question.type, | 
				
			|||
                    class: question.class, | 
				
			|||
                    ttl: 300, | 
				
			|||
                    data: "#v=spf1 include:_spf.existing.com ~all", | 
				
			|||
                }); | 
				
			|||
            } else if (question.type === Packet.TYPE.CAA) { | 
				
			|||
                response.answers.push({ | 
				
			|||
                    name: question.name, | 
				
			|||
                    type: question.type, | 
				
			|||
                    class: question.class, | 
				
			|||
                    ttl: 300, | 
				
			|||
                    flags: 0, | 
				
			|||
                    tag: "issue", | 
				
			|||
                    value: "ca.existing.com", | 
				
			|||
                }); | 
				
			|||
            } | 
				
			|||
 | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        if (question.name === "4.3.2.1.in-addr.arpa") { | 
				
			|||
            if (question.type === Packet.TYPE.PTR) { | 
				
			|||
                response.answers.push({ | 
				
			|||
                    name: question.name, | 
				
			|||
                    type: question.type, | 
				
			|||
                    class: question.class, | 
				
			|||
                    ttl: 300, | 
				
			|||
                    domain: "ptr1.existing.com", | 
				
			|||
                }); | 
				
			|||
            } | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        send(response); | 
				
			|||
    } | 
				
			|||
}); | 
				
			|||
 | 
				
			|||
server.on("listening", () => { | 
				
			|||
    console.log("Listening"); | 
				
			|||
    console.log(server.addresses()); | 
				
			|||
}); | 
				
			|||
 | 
				
			|||
server.on("close", () => { | 
				
			|||
    console.log("server closed"); | 
				
			|||
}); | 
				
			|||
 | 
				
			|||
server.listen({ | 
				
			|||
    udp: 5300 | 
				
			|||
}); | 
				
			|||
 | 
				
			|||
function type(code) { | 
				
			|||
    for (let name in Packet.TYPE) { | 
				
			|||
        if (Packet.TYPE[name] === code) { | 
				
			|||
            return name; | 
				
			|||
        } | 
				
			|||
    } | 
				
			|||
} | 
				
			|||
@ -0,0 +1,3 @@ | 
				
			|||
package-lock.json | 
				
			|||
test.js | 
				
			|||
languages/ | 
				
			|||
@ -0,0 +1,84 @@ | 
				
			|||
// Need to use ES6 to read language files
 | 
				
			|||
 | 
				
			|||
import fs from "fs"; | 
				
			|||
import path from "path"; | 
				
			|||
import util from "util"; | 
				
			|||
 | 
				
			|||
// https://stackoverflow.com/questions/13786160/copy-folder-recursively-in-node-js
 | 
				
			|||
/** | 
				
			|||
 * Look ma, it's cp -R. | 
				
			|||
 * @param {string} src  The path to the thing to copy. | 
				
			|||
 * @param {string} dest The path to the new copy. | 
				
			|||
 */ | 
				
			|||
const copyRecursiveSync = function (src, dest) { | 
				
			|||
    let exists = fs.existsSync(src); | 
				
			|||
    let stats = exists && fs.statSync(src); | 
				
			|||
    let isDirectory = exists && stats.isDirectory(); | 
				
			|||
 | 
				
			|||
    if (isDirectory) { | 
				
			|||
        fs.mkdirSync(dest); | 
				
			|||
        fs.readdirSync(src).forEach(function (childItemName) { | 
				
			|||
            copyRecursiveSync(path.join(src, childItemName), | 
				
			|||
                path.join(dest, childItemName)); | 
				
			|||
        }); | 
				
			|||
    } else { | 
				
			|||
        fs.copyFileSync(src, dest); | 
				
			|||
    } | 
				
			|||
}; | 
				
			|||
 | 
				
			|||
console.log("Arguments:", process.argv) | 
				
			|||
const baseLangCode = process.argv[2] || "en"; | 
				
			|||
console.log("Base Lang: " + baseLangCode); | 
				
			|||
fs.rmdirSync("./languages", { recursive: true }); | 
				
			|||
copyRecursiveSync("../../src/languages", "./languages"); | 
				
			|||
 | 
				
			|||
const en = (await import("./languages/en.js")).default; | 
				
			|||
const baseLang = (await import(`./languages/${baseLangCode}.js`)).default; | 
				
			|||
const files = fs.readdirSync("./languages"); | 
				
			|||
console.log("Files:", files); | 
				
			|||
 | 
				
			|||
for (const file of files) { | 
				
			|||
    if (!file.endsWith(".js")) { | 
				
			|||
        console.log("Skipping " + file) | 
				
			|||
        continue; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    console.log("Processing " + file); | 
				
			|||
    const lang = await import("./languages/" + file); | 
				
			|||
 | 
				
			|||
    let obj; | 
				
			|||
 | 
				
			|||
    if (lang.default) { | 
				
			|||
        obj = lang.default; | 
				
			|||
    } else { | 
				
			|||
        console.log("Empty file"); | 
				
			|||
        obj = { | 
				
			|||
            languageName: "<Your Language name in your language (not in English)>" | 
				
			|||
        }; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    // En first
 | 
				
			|||
    for (const key in en) { | 
				
			|||
        if (! obj[key]) { | 
				
			|||
            obj[key] = en[key]; | 
				
			|||
        } | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    if (baseLang !== en) { | 
				
			|||
        // Base second
 | 
				
			|||
        for (const key in baseLang) { | 
				
			|||
            if (! obj[key]) { | 
				
			|||
                obj[key] = key; | 
				
			|||
            } | 
				
			|||
        } | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    const code = "export default " + util.inspect(obj, { | 
				
			|||
        depth: null, | 
				
			|||
    }); | 
				
			|||
 | 
				
			|||
    fs.writeFileSync(`../../src/languages/${file}`, code); | 
				
			|||
} | 
				
			|||
 | 
				
			|||
fs.rmdirSync("./languages", { recursive: true }); | 
				
			|||
console.log("Done. Fixing formatting by ESLint..."); | 
				
			|||
@ -0,0 +1,12 @@ | 
				
			|||
{ | 
				
			|||
    "name": "update-language-files", | 
				
			|||
    "type": "module", | 
				
			|||
    "version": "1.0.0", | 
				
			|||
    "description": "", | 
				
			|||
    "main": "index.js", | 
				
			|||
    "scripts": { | 
				
			|||
        "test": "echo \"Error: no test specified\" && exit 1" | 
				
			|||
    }, | 
				
			|||
    "author": "", | 
				
			|||
    "license": "ISC" | 
				
			|||
} | 
				
			|||
@ -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 | 
				
			|||
@ -0,0 +1,203 @@ | 
				
			|||
# install.sh is generated by ./extra/install.batsh, do not modify it directly. | 
				
			|||
# "npm run compile-install-script" to compile install.sh | 
				
			|||
# The command is working on Windows PowerShell and Docker for Windows only. | 
				
			|||
# curl -o kuma_install.sh https://raw.githubusercontent.com/louislam/uptime-kuma/master/install.sh && sudo bash kuma_install.sh | 
				
			|||
"echo" "-e" "=====================" | 
				
			|||
"echo" "-e" "Uptime Kuma Installer" | 
				
			|||
"echo" "-e" "=====================" | 
				
			|||
"echo" "-e" "Supported OS: CentOS 7/8, Ubuntu >= 16.04 and Debian" | 
				
			|||
"echo" "-e" "---------------------------------------" | 
				
			|||
"echo" "-e" "This script is designed for Linux and basic usage." | 
				
			|||
"echo" "-e" "For advanced usage, please go to https://github.com/louislam/uptime-kuma/wiki/Installation" | 
				
			|||
"echo" "-e" "---------------------------------------" | 
				
			|||
"echo" "-e" "" | 
				
			|||
"echo" "-e" "Local - Install Uptime Kuma in your current machine with git, Node.js 14 and pm2" | 
				
			|||
"echo" "-e" "Docker - Install Uptime Kuma Docker container" | 
				
			|||
"echo" "-e" "" | 
				
			|||
if [ "$1" != "" ]; then | 
				
			|||
  type="$1" | 
				
			|||
else | 
				
			|||
  "read" "-p" "Which installation method do you prefer? [DOCKER/local]: " "type" | 
				
			|||
fi | 
				
			|||
defaultPort="3001" | 
				
			|||
function checkNode { | 
				
			|||
  local _0 | 
				
			|||
  nodeVersion=$(node -e 'console.log(process.versions.node.split(`.`)[0])') | 
				
			|||
  "echo" "-e" "Node Version: ""$nodeVersion" | 
				
			|||
  _0="12" | 
				
			|||
  if [ $(($nodeVersion < $_0)) == 1 ]; then | 
				
			|||
    "echo" "-e" "Error: Required Node.js 14" | 
				
			|||
    "exit" "1"   | 
				
			|||
fi | 
				
			|||
  if [ "$nodeVersion" == "12" ]; then | 
				
			|||
    "echo" "-e" "Warning: NodeJS ""$nodeVersion"" is not tested."   | 
				
			|||
fi | 
				
			|||
} | 
				
			|||
function deb { | 
				
			|||
  nodeCheck=$(node -v) | 
				
			|||
  apt --yes update | 
				
			|||
  if [ "$nodeCheck" != "" ]; then | 
				
			|||
    "checkNode"  | 
				
			|||
  else | 
				
			|||
    # Old nodejs binary name is "nodejs" | 
				
			|||
    check=$(nodejs --version) | 
				
			|||
    if [ "$check" != "" ]; then | 
				
			|||
      "echo" "-e" "Error: 'node' command is not found, but 'nodejs' command is found. Your NodeJS should be too old." | 
				
			|||
      exit 1     | 
				
			|||
fi | 
				
			|||
    curlCheck=$(curl --version) | 
				
			|||
    if [ "$curlCheck" == "" ]; then | 
				
			|||
      "echo" "-e" "Installing Curl" | 
				
			|||
      apt --yes install curl     | 
				
			|||
fi | 
				
			|||
    "echo" "-e" "Installing Node.js 14" | 
				
			|||
    curl -sL https://deb.nodesource.com/setup_14.x | bash - > log.txt | 
				
			|||
    apt --yes install nodejs | 
				
			|||
    node -v | 
				
			|||
    nodeCheckAgain=$(node -v) | 
				
			|||
    if [ "$nodeCheckAgain" == "" ]; then | 
				
			|||
      "echo" "-e" "Error during Node.js installation" | 
				
			|||
      exit 1     | 
				
			|||
fi | 
				
			|||
  fi | 
				
			|||
  check=$(git --version) | 
				
			|||
  if [ "$check" == "" ]; then | 
				
			|||
    "echo" "-e" "Installing Git" | 
				
			|||
    apt --yes install git   | 
				
			|||
fi | 
				
			|||
} | 
				
			|||
if [ "$type" == "local" ]; then | 
				
			|||
  defaultInstallPath="/opt/uptime-kuma" | 
				
			|||
  if [ -e "/etc/redhat-release" ]; then | 
				
			|||
    os=$("cat" "/etc/redhat-release") | 
				
			|||
    distribution="rhel" | 
				
			|||
  else | 
				
			|||
    if [ -e "/etc/issue" ]; then | 
				
			|||
      os=$(head -n1 /etc/issue | cut -f 1 -d ' ') | 
				
			|||
      if [ "$os" == "Ubuntu" ]; then | 
				
			|||
        distribution="ubuntu"       | 
				
			|||
fi | 
				
			|||
      if [ "$os" == "Debian" ]; then | 
				
			|||
        distribution="debian"       | 
				
			|||
fi     | 
				
			|||
fi | 
				
			|||
  fi | 
				
			|||
  arch=$(uname -i) | 
				
			|||
  "echo" "-e" "Your OS: ""$os" | 
				
			|||
  "echo" "-e" "Distribution: ""$distribution" | 
				
			|||
  "echo" "-e" "Arch: ""$arch" | 
				
			|||
  if [ "$3" != "" ]; then | 
				
			|||
    port="$3" | 
				
			|||
  else | 
				
			|||
    "read" "-p" "Listening Port [$defaultPort]: " "port" | 
				
			|||
    if [ "$port" == "" ]; then | 
				
			|||
      port="$defaultPort"     | 
				
			|||
fi | 
				
			|||
  fi | 
				
			|||
  if [ "$2" != "" ]; then | 
				
			|||
    installPath="$2" | 
				
			|||
  else | 
				
			|||
    "read" "-p" "Installation Path [$defaultInstallPath]: " "installPath" | 
				
			|||
    if [ "$installPath" == "" ]; then | 
				
			|||
      installPath="$defaultInstallPath"     | 
				
			|||
fi | 
				
			|||
  fi | 
				
			|||
  # CentOS | 
				
			|||
  if [ "$distribution" == "rhel" ]; then | 
				
			|||
    nodeCheck=$(node -v) | 
				
			|||
    if [ "$nodeCheck" != "" ]; then | 
				
			|||
      "checkNode"  | 
				
			|||
    else | 
				
			|||
      curlCheck=$(curl --version) | 
				
			|||
      if [ "$curlCheck" == "" ]; then | 
				
			|||
        "echo" "-e" "Installing Curl" | 
				
			|||
        yum -y -q install curl       | 
				
			|||
fi | 
				
			|||
      "echo" "-e" "Installing Node.js 14" | 
				
			|||
      curl -sL https://rpm.nodesource.com/setup_14.x | bash - > log.txt | 
				
			|||
      yum install -y -q nodejs | 
				
			|||
      node -v | 
				
			|||
      nodeCheckAgain=$(node -v) | 
				
			|||
      if [ "$nodeCheckAgain" == "" ]; then | 
				
			|||
        "echo" "-e" "Error during Node.js installation" | 
				
			|||
        exit 1       | 
				
			|||
fi | 
				
			|||
    fi | 
				
			|||
    check=$(git --version) | 
				
			|||
    if [ "$check" == "" ]; then | 
				
			|||
      "echo" "-e" "Installing Git" | 
				
			|||
      yum -y -q install git     | 
				
			|||
fi | 
				
			|||
    # Ubuntu | 
				
			|||
  else | 
				
			|||
    if [ "$distribution" == "ubuntu" ]; then | 
				
			|||
      "deb"  | 
				
			|||
      # Debian | 
				
			|||
    else | 
				
			|||
      if [ "$distribution" == "debian" ]; then | 
				
			|||
        "deb"  | 
				
			|||
      else | 
				
			|||
        # Unknown distribution | 
				
			|||
        error=$((0)) | 
				
			|||
        check=$(git --version) | 
				
			|||
        if [ "$check" == "" ]; then | 
				
			|||
          error=$((1)) | 
				
			|||
          "echo" "-e" "Error: git is missing"         | 
				
			|||
fi | 
				
			|||
        check=$(node -v) | 
				
			|||
        if [ "$check" == "" ]; then | 
				
			|||
          error=$((1)) | 
				
			|||
          "echo" "-e" "Error: node is missing"         | 
				
			|||
fi | 
				
			|||
        if [ $(($error > 0)) == 1 ]; then | 
				
			|||
          "echo" "-e" "Please install above missing software" | 
				
			|||
          exit 1         | 
				
			|||
fi | 
				
			|||
      fi | 
				
			|||
    fi | 
				
			|||
  fi | 
				
			|||
  check=$(pm2 --version) | 
				
			|||
  if [ "$check" == "" ]; then | 
				
			|||
    "echo" "-e" "Installing PM2" | 
				
			|||
    npm install pm2 -g | 
				
			|||
    pm2 startup   | 
				
			|||
fi | 
				
			|||
  mkdir -p $installPath | 
				
			|||
  cd $installPath | 
				
			|||
  git clone https://github.com/louislam/uptime-kuma.git . | 
				
			|||
  npm run setup | 
				
			|||
  pm2 start server/server.js --name uptime-kuma -- --port=$port | 
				
			|||
else | 
				
			|||
  defaultVolume="uptime-kuma" | 
				
			|||
  check=$(docker -v) | 
				
			|||
  if [ "$check" == "" ]; then | 
				
			|||
    "echo" "-e" "Error: docker is not found!" | 
				
			|||
    exit 1   | 
				
			|||
fi | 
				
			|||
  check=$(docker info) | 
				
			|||
  if [[ "$check" == *"Is the docker daemon running"* ]]; then | 
				
			|||
      "echo" "Error: docker is not running" | 
				
			|||
      "exit" "1" | 
				
			|||
    fi | 
				
			|||
  if [ "$3" != "" ]; then | 
				
			|||
    port="$3" | 
				
			|||
  else | 
				
			|||
    "read" "-p" "Expose Port [$defaultPort]: " "port" | 
				
			|||
    if [ "$port" == "" ]; then | 
				
			|||
      port="$defaultPort"     | 
				
			|||
fi | 
				
			|||
  fi | 
				
			|||
  if [ "$2" != "" ]; then | 
				
			|||
    volume="$2" | 
				
			|||
  else | 
				
			|||
    "read" "-p" "Volume Name [$defaultVolume]: " "volume" | 
				
			|||
    if [ "$volume" == "" ]; then | 
				
			|||
      volume="$defaultVolume"     | 
				
			|||
fi | 
				
			|||
  fi | 
				
			|||
  "echo" "-e" "Port: $port" | 
				
			|||
  "echo" "-e" "Volume: $volume" | 
				
			|||
  docker volume create $volume | 
				
			|||
  docker run -d --restart=always -p $port:3001 -v $volume:/app/data --name uptime-kuma louislam/uptime-kuma:1 | 
				
			|||
fi | 
				
			|||
"echo" "-e" "http://localhost:$port" | 
				
			|||
@ -0,0 +1,32 @@ | 
				
			|||
# 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. | 
				
			|||
@ -0,0 +1,10 @@ | 
				
			|||
namespace: uptime-kuma | 
				
			|||
namePrefix: uptime-kuma- | 
				
			|||
 | 
				
			|||
commonLabels: | 
				
			|||
  app: uptime-kuma | 
				
			|||
 | 
				
			|||
bases: | 
				
			|||
   - uptime-kuma | 
				
			|||
 | 
				
			|||
 | 
				
			|||
@ -0,0 +1,45 @@ | 
				
			|||
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 | 
				
			|||
@ -0,0 +1,39 @@ | 
				
			|||
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 | 
				
			|||
@ -0,0 +1,5 @@ | 
				
			|||
resources: | 
				
			|||
  - deployment.yml | 
				
			|||
  - service.yml | 
				
			|||
  - ingressroute.yml | 
				
			|||
  - pvc.yml | 
				
			|||
@ -0,0 +1,10 @@ | 
				
			|||
apiVersion: v1 | 
				
			|||
kind: PersistentVolumeClaim | 
				
			|||
metadata: | 
				
			|||
  name: pvc | 
				
			|||
spec: | 
				
			|||
  accessModes: | 
				
			|||
    - ReadWriteOnce | 
				
			|||
  resources: | 
				
			|||
    requests: | 
				
			|||
      storage: 4Gi | 
				
			|||
@ -0,0 +1,13 @@ | 
				
			|||
apiVersion: v1 | 
				
			|||
kind: Service | 
				
			|||
metadata:   | 
				
			|||
  name: service | 
				
			|||
spec: | 
				
			|||
  selector:     | 
				
			|||
    component: uptime-kuma | 
				
			|||
  type: ClusterIP | 
				
			|||
  ports:   | 
				
			|||
  - name: http | 
				
			|||
    port: 3001 | 
				
			|||
    targetPort: 3001 | 
				
			|||
    protocol: TCP | 
				
			|||
								
									
										File diff suppressed because it is too large
									
								
							
						
					| 
		 After Width: | Height: | Size: 2.6 KiB  | 
| 
		 After Width: | Height: | Size: 9.5 KiB  | 
@ -0,0 +1,19 @@ | 
				
			|||
{ | 
				
			|||
    "name": "Uptime Kuma", | 
				
			|||
    "short_name": "Uptime Kuma", | 
				
			|||
    "start_url": "/", | 
				
			|||
    "background_color": "#fff", | 
				
			|||
    "display": "standalone", | 
				
			|||
    "icons": [ | 
				
			|||
        { | 
				
			|||
            "src": "icon-192x192.png", | 
				
			|||
            "sizes": "192x192", | 
				
			|||
            "type": "image/png" | 
				
			|||
        }, | 
				
			|||
        { | 
				
			|||
            "src": "icon-512x512.png", | 
				
			|||
            "sizes": "512x512", | 
				
			|||
            "type": "image/png" | 
				
			|||
        } | 
				
			|||
    ] | 
				
			|||
} | 
				
			|||
@ -0,0 +1,42 @@ | 
				
			|||
const { setSetting } = require("./util-server"); | 
				
			|||
const axios = require("axios"); | 
				
			|||
 | 
				
			|||
exports.version = require("../package.json").version; | 
				
			|||
exports.latestVersion = null; | 
				
			|||
 | 
				
			|||
let interval; | 
				
			|||
 | 
				
			|||
exports.startInterval = () => { | 
				
			|||
    let check = async () => { | 
				
			|||
        try { | 
				
			|||
            const res = await axios.get("https://raw.githubusercontent.com/louislam/uptime-kuma/master/package.json"); | 
				
			|||
 | 
				
			|||
            if (typeof res.data === "string") { | 
				
			|||
                res.data = JSON.parse(res.data); | 
				
			|||
            } | 
				
			|||
 | 
				
			|||
            // For debug
 | 
				
			|||
            if (process.env.TEST_CHECK_VERSION === "1") { | 
				
			|||
                res.data.version = "1000.0.0"; | 
				
			|||
            } | 
				
			|||
 | 
				
			|||
            exports.latestVersion = res.data.version; | 
				
			|||
        } catch (_) { } | 
				
			|||
 | 
				
			|||
    }; | 
				
			|||
 | 
				
			|||
    check(); | 
				
			|||
    interval = setInterval(check, 3600 * 1000 * 48); | 
				
			|||
}; | 
				
			|||
 | 
				
			|||
exports.enableCheckUpdate = async (value) => { | 
				
			|||
    await setSetting("checkUpdate", value); | 
				
			|||
 | 
				
			|||
    clearInterval(interval); | 
				
			|||
 | 
				
			|||
    if (value) { | 
				
			|||
        exports.startInterval(); | 
				
			|||
    } | 
				
			|||
}; | 
				
			|||
 | 
				
			|||
exports.socket = null; | 
				
			|||
@ -0,0 +1,100 @@ | 
				
			|||
/* | 
				
			|||
 * For Client Socket | 
				
			|||
 */ | 
				
			|||
const { TimeLogger } = require("../src/util"); | 
				
			|||
const { R } = require("redbean-node"); | 
				
			|||
const { io } = require("./server"); | 
				
			|||
const { setting } = require("./util-server"); | 
				
			|||
const checkVersion = require("./check-version"); | 
				
			|||
 | 
				
			|||
async function sendNotificationList(socket) { | 
				
			|||
    const timeLogger = new TimeLogger(); | 
				
			|||
 | 
				
			|||
    let result = []; | 
				
			|||
    let list = await R.find("notification", " user_id = ? ", [ | 
				
			|||
        socket.userID, | 
				
			|||
    ]); | 
				
			|||
 | 
				
			|||
    for (let bean of list) { | 
				
			|||
        result.push(bean.export()); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    io.to(socket.userID).emit("notificationList", result); | 
				
			|||
 | 
				
			|||
    timeLogger.print("Send Notification List"); | 
				
			|||
 | 
				
			|||
    return list; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
/** | 
				
			|||
 * Send Heartbeat History list to socket | 
				
			|||
 * @param toUser  True = send to all browsers with the same user id, False = send to the current browser only | 
				
			|||
 * @param overwrite Overwrite client-side's heartbeat list | 
				
			|||
 */ | 
				
			|||
async function sendHeartbeatList(socket, monitorID, toUser = false, overwrite = false) { | 
				
			|||
    const timeLogger = new TimeLogger(); | 
				
			|||
 | 
				
			|||
    let list = await R.getAll(` | 
				
			|||
        SELECT * FROM heartbeat | 
				
			|||
        WHERE monitor_id = ? | 
				
			|||
        ORDER BY time DESC | 
				
			|||
        LIMIT 100 | 
				
			|||
    `, [
 | 
				
			|||
        monitorID, | 
				
			|||
    ]); | 
				
			|||
 | 
				
			|||
    let result = list.reverse(); | 
				
			|||
 | 
				
			|||
    if (toUser) { | 
				
			|||
        io.to(socket.userID).emit("heartbeatList", monitorID, result, overwrite); | 
				
			|||
    } else { | 
				
			|||
        socket.emit("heartbeatList", monitorID, result, overwrite); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    timeLogger.print(`[Monitor: ${monitorID}] sendHeartbeatList`); | 
				
			|||
} | 
				
			|||
 | 
				
			|||
/** | 
				
			|||
 *  Important Heart beat list (aka event list) | 
				
			|||
 * @param socket | 
				
			|||
 * @param monitorID | 
				
			|||
 * @param toUser  True = send to all browsers with the same user id, False = send to the current browser only | 
				
			|||
 * @param overwrite Overwrite client-side's heartbeat list | 
				
			|||
 */ | 
				
			|||
async function sendImportantHeartbeatList(socket, monitorID, toUser = false, overwrite = false) { | 
				
			|||
    const timeLogger = new TimeLogger(); | 
				
			|||
 | 
				
			|||
    let list = await R.find("heartbeat", ` | 
				
			|||
        monitor_id = ? | 
				
			|||
        AND important = 1 | 
				
			|||
        ORDER BY time DESC | 
				
			|||
        LIMIT 500 | 
				
			|||
    `, [
 | 
				
			|||
        monitorID, | 
				
			|||
    ]); | 
				
			|||
 | 
				
			|||
    timeLogger.print(`[Monitor: ${monitorID}] sendImportantHeartbeatList`); | 
				
			|||
 | 
				
			|||
    if (toUser) { | 
				
			|||
        io.to(socket.userID).emit("importantHeartbeatList", monitorID, list, overwrite); | 
				
			|||
    } else { | 
				
			|||
        socket.emit("importantHeartbeatList", monitorID, list, overwrite); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
} | 
				
			|||
 | 
				
			|||
async function sendInfo(socket) { | 
				
			|||
    socket.emit("info", { | 
				
			|||
        version: checkVersion.version, | 
				
			|||
        latestVersion: checkVersion.latestVersion, | 
				
			|||
        primaryBaseURL: await setting("primaryBaseURL") | 
				
			|||
    }); | 
				
			|||
} | 
				
			|||
 | 
				
			|||
module.exports = { | 
				
			|||
    sendNotificationList, | 
				
			|||
    sendImportantHeartbeatList, | 
				
			|||
    sendHeartbeatList, | 
				
			|||
    sendInfo | 
				
			|||
}; | 
				
			|||
 | 
				
			|||
@ -0,0 +1,57 @@ | 
				
			|||
/* | 
				
			|||
    From https://github.com/DiegoZoracKy/image-data-uri/blob/master/lib/image-data-uri.js
 | 
				
			|||
    Modified with 0 dependencies | 
				
			|||
 */ | 
				
			|||
let fs = require("fs"); | 
				
			|||
 | 
				
			|||
let ImageDataURI = (() => { | 
				
			|||
 | 
				
			|||
    function decode(dataURI) { | 
				
			|||
        if (!/data:image\//.test(dataURI)) { | 
				
			|||
            console.log("ImageDataURI :: Error :: It seems that it is not an Image Data URI. Couldn't match \"data:image/\""); | 
				
			|||
            return null; | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        let regExMatches = dataURI.match("data:(image/.*);base64,(.*)"); | 
				
			|||
        return { | 
				
			|||
            imageType: regExMatches[1], | 
				
			|||
            dataBase64: regExMatches[2], | 
				
			|||
            dataBuffer: new Buffer(regExMatches[2], "base64") | 
				
			|||
        }; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    function encode(data, mediaType) { | 
				
			|||
        if (!data || !mediaType) { | 
				
			|||
            console.log("ImageDataURI :: Error :: Missing some of the required params: data, mediaType "); | 
				
			|||
            return null; | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        mediaType = (/\//.test(mediaType)) ? mediaType : "image/" + mediaType; | 
				
			|||
        let dataBase64 = (Buffer.isBuffer(data)) ? data.toString("base64") : new Buffer(data).toString("base64"); | 
				
			|||
        let dataImgBase64 = "data:" + mediaType + ";base64," + dataBase64; | 
				
			|||
 | 
				
			|||
        return dataImgBase64; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    function outputFile(dataURI, filePath) { | 
				
			|||
        filePath = filePath || "./"; | 
				
			|||
        return new Promise((resolve, reject) => { | 
				
			|||
            let imageDecoded = decode(dataURI); | 
				
			|||
 | 
				
			|||
            fs.writeFile(filePath, imageDecoded.dataBuffer, err => { | 
				
			|||
                if (err) { | 
				
			|||
                    return reject("ImageDataURI :: Error :: " + JSON.stringify(err, null, 4)); | 
				
			|||
                } | 
				
			|||
                resolve(filePath); | 
				
			|||
            }); | 
				
			|||
        }); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    return { | 
				
			|||
        decode: decode, | 
				
			|||
        encode: encode, | 
				
			|||
        outputFile: outputFile, | 
				
			|||
    }; | 
				
			|||
})(); | 
				
			|||
 | 
				
			|||
module.exports = ImageDataURI; | 
				
			|||
@ -0,0 +1,34 @@ | 
				
			|||
const { BeanModel } = require("redbean-node/dist/bean-model"); | 
				
			|||
const { R } = require("redbean-node"); | 
				
			|||
 | 
				
			|||
class Group extends BeanModel { | 
				
			|||
 | 
				
			|||
    async toPublicJSON() { | 
				
			|||
        let monitorBeanList = await this.getMonitorList(); | 
				
			|||
        let monitorList = []; | 
				
			|||
 | 
				
			|||
        for (let bean of monitorBeanList) { | 
				
			|||
            monitorList.push(await bean.toPublicJSON()); | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        return { | 
				
			|||
            id: this.id, | 
				
			|||
            name: this.name, | 
				
			|||
            weight: this.weight, | 
				
			|||
            monitorList, | 
				
			|||
        }; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    async getMonitorList() { | 
				
			|||
        return R.convertToBeans("monitor", await R.getAll(` | 
				
			|||
            SELECT monitor.* FROM monitor, monitor_group | 
				
			|||
            WHERE monitor.id = monitor_group.monitor_id | 
				
			|||
            AND group_id = ? | 
				
			|||
            ORDER BY monitor_group.weight | 
				
			|||
        `, [
 | 
				
			|||
            this.id, | 
				
			|||
        ])); | 
				
			|||
    } | 
				
			|||
} | 
				
			|||
 | 
				
			|||
module.exports = Group; | 
				
			|||
@ -0,0 +1,18 @@ | 
				
			|||
const { BeanModel } = require("redbean-node/dist/bean-model"); | 
				
			|||
 | 
				
			|||
class Incident extends BeanModel { | 
				
			|||
 | 
				
			|||
    toPublicJSON() { | 
				
			|||
        return { | 
				
			|||
            id: this.id, | 
				
			|||
            style: this.style, | 
				
			|||
            title: this.title, | 
				
			|||
            content: this.content, | 
				
			|||
            pin: this.pin, | 
				
			|||
            createdDate: this.createdDate, | 
				
			|||
            lastUpdatedDate: this.lastUpdatedDate, | 
				
			|||
        }; | 
				
			|||
    } | 
				
			|||
} | 
				
			|||
 | 
				
			|||
module.exports = Incident; | 
				
			|||
@ -0,0 +1,13 @@ | 
				
			|||
const { BeanModel } = require("redbean-node/dist/bean-model"); | 
				
			|||
 | 
				
			|||
class Tag extends BeanModel { | 
				
			|||
    toJSON() { | 
				
			|||
        return { | 
				
			|||
            id: this._id, | 
				
			|||
            name: this._name, | 
				
			|||
            color: this._color, | 
				
			|||
        }; | 
				
			|||
    } | 
				
			|||
} | 
				
			|||
 | 
				
			|||
module.exports = Tag; | 
				
			|||
@ -0,0 +1,749 @@ | 
				
			|||
let url = require("url"); | 
				
			|||
let MemoryCache = require("./memory-cache"); | 
				
			|||
 | 
				
			|||
let t = { | 
				
			|||
    ms: 1, | 
				
			|||
    second: 1000, | 
				
			|||
    minute: 60000, | 
				
			|||
    hour: 3600000, | 
				
			|||
    day: 3600000 * 24, | 
				
			|||
    week: 3600000 * 24 * 7, | 
				
			|||
    month: 3600000 * 24 * 30, | 
				
			|||
}; | 
				
			|||
 | 
				
			|||
let instances = []; | 
				
			|||
 | 
				
			|||
let matches = function (a) { | 
				
			|||
    return function (b) { | 
				
			|||
        return a === b; | 
				
			|||
    }; | 
				
			|||
}; | 
				
			|||
 | 
				
			|||
let doesntMatch = function (a) { | 
				
			|||
    return function (b) { | 
				
			|||
        return !matches(a)(b); | 
				
			|||
    }; | 
				
			|||
}; | 
				
			|||
 | 
				
			|||
let logDuration = function (d, prefix) { | 
				
			|||
    let str = d > 1000 ? (d / 1000).toFixed(2) + "sec" : d + "ms"; | 
				
			|||
    return "\x1b[33m- " + (prefix ? prefix + " " : "") + str + "\x1b[0m"; | 
				
			|||
}; | 
				
			|||
 | 
				
			|||
function getSafeHeaders(res) { | 
				
			|||
    return res.getHeaders ? res.getHeaders() : res._headers; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
function ApiCache() { | 
				
			|||
    let memCache = new MemoryCache(); | 
				
			|||
 | 
				
			|||
    let globalOptions = { | 
				
			|||
        debug: false, | 
				
			|||
        defaultDuration: 3600000, | 
				
			|||
        enabled: true, | 
				
			|||
        appendKey: [], | 
				
			|||
        jsonp: false, | 
				
			|||
        redisClient: false, | 
				
			|||
        headerBlacklist: [], | 
				
			|||
        statusCodes: { | 
				
			|||
            include: [], | 
				
			|||
            exclude: [], | 
				
			|||
        }, | 
				
			|||
        events: { | 
				
			|||
            expire: undefined, | 
				
			|||
        }, | 
				
			|||
        headers: { | 
				
			|||
            // 'cache-control':  'no-cache' // example of header overwrite
 | 
				
			|||
        }, | 
				
			|||
        trackPerformance: false, | 
				
			|||
        respectCacheControl: false, | 
				
			|||
    }; | 
				
			|||
 | 
				
			|||
    let middlewareOptions = []; | 
				
			|||
    let instance = this; | 
				
			|||
    let index = null; | 
				
			|||
    let timers = {}; | 
				
			|||
    let performanceArray = []; // for tracking cache hit rate
 | 
				
			|||
 | 
				
			|||
    instances.push(this); | 
				
			|||
    this.id = instances.length; | 
				
			|||
 | 
				
			|||
    function debug(a, b, c, d) { | 
				
			|||
        let arr = ["\x1b[36m[apicache]\x1b[0m", a, b, c, d].filter(function (arg) { | 
				
			|||
            return arg !== undefined; | 
				
			|||
        }); | 
				
			|||
        let debugEnv = process.env.DEBUG && process.env.DEBUG.split(",").indexOf("apicache") !== -1; | 
				
			|||
 | 
				
			|||
        return (globalOptions.debug || debugEnv) && console.log.apply(null, arr); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    function shouldCacheResponse(request, response, toggle) { | 
				
			|||
        let opt = globalOptions; | 
				
			|||
        let codes = opt.statusCodes; | 
				
			|||
 | 
				
			|||
        if (!response) { | 
				
			|||
            return false; | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        if (toggle && !toggle(request, response)) { | 
				
			|||
            return false; | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        if (codes.exclude && codes.exclude.length && codes.exclude.indexOf(response.statusCode) !== -1) { | 
				
			|||
            return false; | 
				
			|||
        } | 
				
			|||
        if (codes.include && codes.include.length && codes.include.indexOf(response.statusCode) === -1) { | 
				
			|||
            return false; | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        return true; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    function addIndexEntries(key, req) { | 
				
			|||
        let groupName = req.apicacheGroup; | 
				
			|||
 | 
				
			|||
        if (groupName) { | 
				
			|||
            debug("group detected \"" + groupName + "\""); | 
				
			|||
            let group = (index.groups[groupName] = index.groups[groupName] || []); | 
				
			|||
            group.unshift(key); | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        index.all.unshift(key); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    function filterBlacklistedHeaders(headers) { | 
				
			|||
        return Object.keys(headers) | 
				
			|||
            .filter(function (key) { | 
				
			|||
                return globalOptions.headerBlacklist.indexOf(key) === -1; | 
				
			|||
            }) | 
				
			|||
            .reduce(function (acc, header) { | 
				
			|||
                acc[header] = headers[header]; | 
				
			|||
                return acc; | 
				
			|||
            }, {}); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    function createCacheObject(status, headers, data, encoding) { | 
				
			|||
        return { | 
				
			|||
            status: status, | 
				
			|||
            headers: filterBlacklistedHeaders(headers), | 
				
			|||
            data: data, | 
				
			|||
            encoding: encoding, | 
				
			|||
            timestamp: new Date().getTime() / 1000, // seconds since epoch.  This is used to properly decrement max-age headers in cached responses.
 | 
				
			|||
        }; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    function cacheResponse(key, value, duration) { | 
				
			|||
        let redis = globalOptions.redisClient; | 
				
			|||
        let expireCallback = globalOptions.events.expire; | 
				
			|||
 | 
				
			|||
        if (redis && redis.connected) { | 
				
			|||
            try { | 
				
			|||
                redis.hset(key, "response", JSON.stringify(value)); | 
				
			|||
                redis.hset(key, "duration", duration); | 
				
			|||
                redis.expire(key, duration / 1000, expireCallback || function () {}); | 
				
			|||
            } catch (err) { | 
				
			|||
                debug("[apicache] error in redis.hset()"); | 
				
			|||
            } | 
				
			|||
        } else { | 
				
			|||
            memCache.add(key, value, duration, expireCallback); | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        // add automatic cache clearing from duration, includes max limit on setTimeout
 | 
				
			|||
        timers[key] = setTimeout(function () { | 
				
			|||
            instance.clear(key, true); | 
				
			|||
        }, Math.min(duration, 2147483647)); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    function accumulateContent(res, content) { | 
				
			|||
        if (content) { | 
				
			|||
            if (typeof content == "string") { | 
				
			|||
                res._apicache.content = (res._apicache.content || "") + content; | 
				
			|||
            } else if (Buffer.isBuffer(content)) { | 
				
			|||
                let oldContent = res._apicache.content; | 
				
			|||
 | 
				
			|||
                if (typeof oldContent === "string") { | 
				
			|||
                    oldContent = !Buffer.from ? new Buffer(oldContent) : Buffer.from(oldContent); | 
				
			|||
                } | 
				
			|||
 | 
				
			|||
                if (!oldContent) { | 
				
			|||
                    oldContent = !Buffer.alloc ? new Buffer(0) : Buffer.alloc(0); | 
				
			|||
                } | 
				
			|||
 | 
				
			|||
                res._apicache.content = Buffer.concat( | 
				
			|||
                    [oldContent, content], | 
				
			|||
                    oldContent.length + content.length | 
				
			|||
                ); | 
				
			|||
            } else { | 
				
			|||
                res._apicache.content = content; | 
				
			|||
            } | 
				
			|||
        } | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    function makeResponseCacheable(req, res, next, key, duration, strDuration, toggle) { | 
				
			|||
    // monkeypatch res.end to create cache object
 | 
				
			|||
        res._apicache = { | 
				
			|||
            write: res.write, | 
				
			|||
            writeHead: res.writeHead, | 
				
			|||
            end: res.end, | 
				
			|||
            cacheable: true, | 
				
			|||
            content: undefined, | 
				
			|||
        }; | 
				
			|||
 | 
				
			|||
        // append header overwrites if applicable
 | 
				
			|||
        Object.keys(globalOptions.headers).forEach(function (name) { | 
				
			|||
            res.setHeader(name, globalOptions.headers[name]); | 
				
			|||
        }); | 
				
			|||
 | 
				
			|||
        res.writeHead = function () { | 
				
			|||
            // add cache control headers
 | 
				
			|||
            if (!globalOptions.headers["cache-control"]) { | 
				
			|||
                if (shouldCacheResponse(req, res, toggle)) { | 
				
			|||
                    res.setHeader("cache-control", "max-age=" + (duration / 1000).toFixed(0)); | 
				
			|||
                } else { | 
				
			|||
                    res.setHeader("cache-control", "no-cache, no-store, must-revalidate"); | 
				
			|||
                } | 
				
			|||
            } | 
				
			|||
 | 
				
			|||
            res._apicache.headers = Object.assign({}, getSafeHeaders(res)); | 
				
			|||
            return res._apicache.writeHead.apply(this, arguments); | 
				
			|||
        }; | 
				
			|||
 | 
				
			|||
        // patch res.write
 | 
				
			|||
        res.write = function (content) { | 
				
			|||
            accumulateContent(res, content); | 
				
			|||
            return res._apicache.write.apply(this, arguments); | 
				
			|||
        }; | 
				
			|||
 | 
				
			|||
        // patch res.end
 | 
				
			|||
        res.end = function (content, encoding) { | 
				
			|||
            if (shouldCacheResponse(req, res, toggle)) { | 
				
			|||
                accumulateContent(res, content); | 
				
			|||
 | 
				
			|||
                if (res._apicache.cacheable && res._apicache.content) { | 
				
			|||
                    addIndexEntries(key, req); | 
				
			|||
                    let headers = res._apicache.headers || getSafeHeaders(res); | 
				
			|||
                    let cacheObject = createCacheObject( | 
				
			|||
                        res.statusCode, | 
				
			|||
                        headers, | 
				
			|||
                        res._apicache.content, | 
				
			|||
                        encoding | 
				
			|||
                    ); | 
				
			|||
                    cacheResponse(key, cacheObject, duration); | 
				
			|||
 | 
				
			|||
                    // display log entry
 | 
				
			|||
                    let elapsed = new Date() - req.apicacheTimer; | 
				
			|||
                    debug("adding cache entry for \"" + key + "\" @ " + strDuration, logDuration(elapsed)); | 
				
			|||
                    debug("_apicache.headers: ", res._apicache.headers); | 
				
			|||
                    debug("res.getHeaders(): ", getSafeHeaders(res)); | 
				
			|||
                    debug("cacheObject: ", cacheObject); | 
				
			|||
                } | 
				
			|||
            } | 
				
			|||
 | 
				
			|||
            return res._apicache.end.apply(this, arguments); | 
				
			|||
        }; | 
				
			|||
 | 
				
			|||
        next(); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    function sendCachedResponse(request, response, cacheObject, toggle, next, duration) { | 
				
			|||
        if (toggle && !toggle(request, response)) { | 
				
			|||
            return next(); | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        let headers = getSafeHeaders(response); | 
				
			|||
 | 
				
			|||
        // Modified by @louislam, removed Cache-control, since I don't need client side cache!
 | 
				
			|||
        // Original Source: https://github.com/kwhitley/apicache/blob/0d5686cc21fad353c6dddee646288c2fca3e4f50/src/apicache.js#L254
 | 
				
			|||
        Object.assign(headers, filterBlacklistedHeaders(cacheObject.headers || {})); | 
				
			|||
 | 
				
			|||
        // only embed apicache headers when not in production environment
 | 
				
			|||
        if (process.env.NODE_ENV !== "production") { | 
				
			|||
            Object.assign(headers, { | 
				
			|||
                "apicache-store": globalOptions.redisClient ? "redis" : "memory", | 
				
			|||
                "apicache-version": "1.6.2-modified", | 
				
			|||
            }); | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        // unstringify buffers
 | 
				
			|||
        let data = cacheObject.data; | 
				
			|||
        if (data && data.type === "Buffer") { | 
				
			|||
            data = | 
				
			|||
        typeof data.data === "number" ? new Buffer.alloc(data.data) : new Buffer.from(data.data); | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        // test Etag against If-None-Match for 304
 | 
				
			|||
        let cachedEtag = cacheObject.headers.etag; | 
				
			|||
        let requestEtag = request.headers["if-none-match"]; | 
				
			|||
 | 
				
			|||
        if (requestEtag && cachedEtag === requestEtag) { | 
				
			|||
            response.writeHead(304, headers); | 
				
			|||
            return response.end(); | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        response.writeHead(cacheObject.status || 200, headers); | 
				
			|||
 | 
				
			|||
        return response.end(data, cacheObject.encoding); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    function syncOptions() { | 
				
			|||
        for (let i in middlewareOptions) { | 
				
			|||
            Object.assign(middlewareOptions[i].options, globalOptions, middlewareOptions[i].localOptions); | 
				
			|||
        } | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    this.clear = function (target, isAutomatic) { | 
				
			|||
        let group = index.groups[target]; | 
				
			|||
        let redis = globalOptions.redisClient; | 
				
			|||
 | 
				
			|||
        if (group) { | 
				
			|||
            debug("clearing group \"" + target + "\""); | 
				
			|||
 | 
				
			|||
            group.forEach(function (key) { | 
				
			|||
                debug("clearing cached entry for \"" + key + "\""); | 
				
			|||
                clearTimeout(timers[key]); | 
				
			|||
                delete timers[key]; | 
				
			|||
                if (!globalOptions.redisClient) { | 
				
			|||
                    memCache.delete(key); | 
				
			|||
                } else { | 
				
			|||
                    try { | 
				
			|||
                        redis.del(key); | 
				
			|||
                    } catch (err) { | 
				
			|||
                        console.log("[apicache] error in redis.del(\"" + key + "\")"); | 
				
			|||
                    } | 
				
			|||
                } | 
				
			|||
                index.all = index.all.filter(doesntMatch(key)); | 
				
			|||
            }); | 
				
			|||
 | 
				
			|||
            delete index.groups[target]; | 
				
			|||
        } else if (target) { | 
				
			|||
            debug("clearing " + (isAutomatic ? "expired" : "cached") + " entry for \"" + target + "\""); | 
				
			|||
            clearTimeout(timers[target]); | 
				
			|||
            delete timers[target]; | 
				
			|||
            // clear actual cached entry
 | 
				
			|||
            if (!redis) { | 
				
			|||
                memCache.delete(target); | 
				
			|||
            } else { | 
				
			|||
                try { | 
				
			|||
                    redis.del(target); | 
				
			|||
                } catch (err) { | 
				
			|||
                    console.log("[apicache] error in redis.del(\"" + target + "\")"); | 
				
			|||
                } | 
				
			|||
            } | 
				
			|||
 | 
				
			|||
            // remove from global index
 | 
				
			|||
            index.all = index.all.filter(doesntMatch(target)); | 
				
			|||
 | 
				
			|||
            // remove target from each group that it may exist in
 | 
				
			|||
            Object.keys(index.groups).forEach(function (groupName) { | 
				
			|||
                index.groups[groupName] = index.groups[groupName].filter(doesntMatch(target)); | 
				
			|||
 | 
				
			|||
                // delete group if now empty
 | 
				
			|||
                if (!index.groups[groupName].length) { | 
				
			|||
                    delete index.groups[groupName]; | 
				
			|||
                } | 
				
			|||
            }); | 
				
			|||
        } else { | 
				
			|||
            debug("clearing entire index"); | 
				
			|||
 | 
				
			|||
            if (!redis) { | 
				
			|||
                memCache.clear(); | 
				
			|||
            } else { | 
				
			|||
                // clear redis keys one by one from internal index to prevent clearing non-apicache entries
 | 
				
			|||
                index.all.forEach(function (key) { | 
				
			|||
                    clearTimeout(timers[key]); | 
				
			|||
                    delete timers[key]; | 
				
			|||
                    try { | 
				
			|||
                        redis.del(key); | 
				
			|||
                    } catch (err) { | 
				
			|||
                        console.log("[apicache] error in redis.del(\"" + key + "\")"); | 
				
			|||
                    } | 
				
			|||
                }); | 
				
			|||
            } | 
				
			|||
            this.resetIndex(); | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        return this.getIndex(); | 
				
			|||
    }; | 
				
			|||
 | 
				
			|||
    function parseDuration(duration, defaultDuration) { | 
				
			|||
        if (typeof duration === "number") { | 
				
			|||
            return duration; | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        if (typeof duration === "string") { | 
				
			|||
            let split = duration.match(/^([\d\.,]+)\s?(\w+)$/); | 
				
			|||
 | 
				
			|||
            if (split.length === 3) { | 
				
			|||
                let len = parseFloat(split[1]); | 
				
			|||
                let unit = split[2].replace(/s$/i, "").toLowerCase(); | 
				
			|||
                if (unit === "m") { | 
				
			|||
                    unit = "ms"; | 
				
			|||
                } | 
				
			|||
 | 
				
			|||
                return (len || 1) * (t[unit] || 0); | 
				
			|||
            } | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        return defaultDuration; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    this.getDuration = function (duration) { | 
				
			|||
        return parseDuration(duration, globalOptions.defaultDuration); | 
				
			|||
    }; | 
				
			|||
 | 
				
			|||
    /** | 
				
			|||
   * Return cache performance statistics (hit rate).  Suitable for putting into a route: | 
				
			|||
   * <code> | 
				
			|||
   * app.get('/api/cache/performance', (req, res) => { | 
				
			|||
   *    res.json(apicache.getPerformance()) | 
				
			|||
   * }) | 
				
			|||
   * </code> | 
				
			|||
   */ | 
				
			|||
    this.getPerformance = function () { | 
				
			|||
        return performanceArray.map(function (p) { | 
				
			|||
            return p.report(); | 
				
			|||
        }); | 
				
			|||
    }; | 
				
			|||
 | 
				
			|||
    this.getIndex = function (group) { | 
				
			|||
        if (group) { | 
				
			|||
            return index.groups[group]; | 
				
			|||
        } else { | 
				
			|||
            return index; | 
				
			|||
        } | 
				
			|||
    }; | 
				
			|||
 | 
				
			|||
    this.middleware = function cache(strDuration, middlewareToggle, localOptions) { | 
				
			|||
        let duration = instance.getDuration(strDuration); | 
				
			|||
        let opt = {}; | 
				
			|||
 | 
				
			|||
        middlewareOptions.push({ | 
				
			|||
            options: opt, | 
				
			|||
        }); | 
				
			|||
 | 
				
			|||
        let options = function (localOptions) { | 
				
			|||
            if (localOptions) { | 
				
			|||
                middlewareOptions.find(function (middleware) { | 
				
			|||
                    return middleware.options === opt; | 
				
			|||
                }).localOptions = localOptions; | 
				
			|||
            } | 
				
			|||
 | 
				
			|||
            syncOptions(); | 
				
			|||
 | 
				
			|||
            return opt; | 
				
			|||
        }; | 
				
			|||
 | 
				
			|||
        options(localOptions); | 
				
			|||
 | 
				
			|||
        /** | 
				
			|||
     * A Function for non tracking performance | 
				
			|||
     */ | 
				
			|||
        function NOOPCachePerformance() { | 
				
			|||
            this.report = this.hit = this.miss = function () {}; // noop;
 | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        /** | 
				
			|||
     * A function for tracking and reporting hit rate.  These statistics are returned by the getPerformance() call above. | 
				
			|||
     */ | 
				
			|||
        function CachePerformance() { | 
				
			|||
            /** | 
				
			|||
       * Tracks the hit rate for the last 100 requests. | 
				
			|||
       * If there have been fewer than 100 requests, the hit rate just considers the requests that have happened. | 
				
			|||
       */ | 
				
			|||
            this.hitsLast100 = new Uint8Array(100 / 4); // each hit is 2 bits
 | 
				
			|||
 | 
				
			|||
            /** | 
				
			|||
       * Tracks the hit rate for the last 1000 requests. | 
				
			|||
       * If there have been fewer than 1000 requests, the hit rate just considers the requests that have happened. | 
				
			|||
       */ | 
				
			|||
            this.hitsLast1000 = new Uint8Array(1000 / 4); // each hit is 2 bits
 | 
				
			|||
 | 
				
			|||
            /** | 
				
			|||
       * Tracks the hit rate for the last 10000 requests. | 
				
			|||
       * If there have been fewer than 10000 requests, the hit rate just considers the requests that have happened. | 
				
			|||
       */ | 
				
			|||
            this.hitsLast10000 = new Uint8Array(10000 / 4); // each hit is 2 bits
 | 
				
			|||
 | 
				
			|||
            /** | 
				
			|||
       * Tracks the hit rate for the last 100000 requests. | 
				
			|||
       * If there have been fewer than 100000 requests, the hit rate just considers the requests that have happened. | 
				
			|||
       */ | 
				
			|||
            this.hitsLast100000 = new Uint8Array(100000 / 4); // each hit is 2 bits
 | 
				
			|||
 | 
				
			|||
            /** | 
				
			|||
       * The number of calls that have passed through the middleware since the server started. | 
				
			|||
       */ | 
				
			|||
            this.callCount = 0; | 
				
			|||
 | 
				
			|||
            /** | 
				
			|||
       * The total number of hits since the server started | 
				
			|||
       */ | 
				
			|||
            this.hitCount = 0; | 
				
			|||
 | 
				
			|||
            /** | 
				
			|||
       * The key from the last cache hit.  This is useful in identifying which route these statistics apply to. | 
				
			|||
       */ | 
				
			|||
            this.lastCacheHit = null; | 
				
			|||
 | 
				
			|||
            /** | 
				
			|||
       * The key from the last cache miss.  This is useful in identifying which route these statistics apply to. | 
				
			|||
       */ | 
				
			|||
            this.lastCacheMiss = null; | 
				
			|||
 | 
				
			|||
            /** | 
				
			|||
       * Return performance statistics | 
				
			|||
       */ | 
				
			|||
            this.report = function () { | 
				
			|||
                return { | 
				
			|||
                    lastCacheHit: this.lastCacheHit, | 
				
			|||
                    lastCacheMiss: this.lastCacheMiss, | 
				
			|||
                    callCount: this.callCount, | 
				
			|||
                    hitCount: this.hitCount, | 
				
			|||
                    missCount: this.callCount - this.hitCount, | 
				
			|||
                    hitRate: this.callCount == 0 ? null : this.hitCount / this.callCount, | 
				
			|||
                    hitRateLast100: this.hitRate(this.hitsLast100), | 
				
			|||
                    hitRateLast1000: this.hitRate(this.hitsLast1000), | 
				
			|||
                    hitRateLast10000: this.hitRate(this.hitsLast10000), | 
				
			|||
                    hitRateLast100000: this.hitRate(this.hitsLast100000), | 
				
			|||
                }; | 
				
			|||
            }; | 
				
			|||
 | 
				
			|||
            /** | 
				
			|||
       * Computes a cache hit rate from an array of hits and misses. | 
				
			|||
       * @param {Uint8Array} array An array representing hits and misses. | 
				
			|||
       * @returns a number between 0 and 1, or null if the array has no hits or misses | 
				
			|||
       */ | 
				
			|||
            this.hitRate = function (array) { | 
				
			|||
                let hits = 0; | 
				
			|||
                let misses = 0; | 
				
			|||
                for (let i = 0; i < array.length; i++) { | 
				
			|||
                    let n8 = array[i]; | 
				
			|||
                    for (let j = 0; j < 4; j++) { | 
				
			|||
                        switch (n8 & 3) { | 
				
			|||
                            case 1: | 
				
			|||
                                hits++; | 
				
			|||
                                break; | 
				
			|||
                            case 2: | 
				
			|||
                                misses++; | 
				
			|||
                                break; | 
				
			|||
                        } | 
				
			|||
                        n8 >>= 2; | 
				
			|||
                    } | 
				
			|||
                } | 
				
			|||
                let total = hits + misses; | 
				
			|||
                if (total == 0) { | 
				
			|||
                    return null; | 
				
			|||
                } | 
				
			|||
                return hits / total; | 
				
			|||
            }; | 
				
			|||
 | 
				
			|||
            /** | 
				
			|||
       * Record a hit or miss in the given array.  It will be recorded at a position determined | 
				
			|||
       * by the current value of the callCount variable. | 
				
			|||
       * @param {Uint8Array} array An array representing hits and misses. | 
				
			|||
       * @param {boolean} hit true for a hit, false for a miss | 
				
			|||
       * Each element in the array is 8 bits, and encodes 4 hit/miss records. | 
				
			|||
       * Each hit or miss is encoded as to bits as follows: | 
				
			|||
       * 00 means no hit or miss has been recorded in these bits | 
				
			|||
       * 01 encodes a hit | 
				
			|||
       * 10 encodes a miss | 
				
			|||
       */ | 
				
			|||
            this.recordHitInArray = function (array, hit) { | 
				
			|||
                let arrayIndex = ~~(this.callCount / 4) % array.length; | 
				
			|||
                let bitOffset = (this.callCount % 4) * 2; // 2 bits per record, 4 records per uint8 array element
 | 
				
			|||
                let clearMask = ~(3 << bitOffset); | 
				
			|||
                let record = (hit ? 1 : 2) << bitOffset; | 
				
			|||
                array[arrayIndex] = (array[arrayIndex] & clearMask) | record; | 
				
			|||
            }; | 
				
			|||
 | 
				
			|||
            /** | 
				
			|||
       * Records the hit or miss in the tracking arrays and increments the call count. | 
				
			|||
       * @param {boolean} hit true records a hit, false records a miss | 
				
			|||
       */ | 
				
			|||
            this.recordHit = function (hit) { | 
				
			|||
                this.recordHitInArray(this.hitsLast100, hit); | 
				
			|||
                this.recordHitInArray(this.hitsLast1000, hit); | 
				
			|||
                this.recordHitInArray(this.hitsLast10000, hit); | 
				
			|||
                this.recordHitInArray(this.hitsLast100000, hit); | 
				
			|||
                if (hit) { | 
				
			|||
                    this.hitCount++; | 
				
			|||
                } | 
				
			|||
                this.callCount++; | 
				
			|||
            }; | 
				
			|||
 | 
				
			|||
            /** | 
				
			|||
       * Records a hit event, setting lastCacheMiss to the given key | 
				
			|||
       * @param {string} key The key that had the cache hit | 
				
			|||
       */ | 
				
			|||
            this.hit = function (key) { | 
				
			|||
                this.recordHit(true); | 
				
			|||
                this.lastCacheHit = key; | 
				
			|||
            }; | 
				
			|||
 | 
				
			|||
            /** | 
				
			|||
       * Records a miss event, setting lastCacheMiss to the given key | 
				
			|||
       * @param {string} key The key that had the cache miss | 
				
			|||
       */ | 
				
			|||
            this.miss = function (key) { | 
				
			|||
                this.recordHit(false); | 
				
			|||
                this.lastCacheMiss = key; | 
				
			|||
            }; | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        let perf = globalOptions.trackPerformance ? new CachePerformance() : new NOOPCachePerformance(); | 
				
			|||
 | 
				
			|||
        performanceArray.push(perf); | 
				
			|||
 | 
				
			|||
        let cache = function (req, res, next) { | 
				
			|||
            function bypass() { | 
				
			|||
                debug("bypass detected, skipping cache."); | 
				
			|||
                return next(); | 
				
			|||
            } | 
				
			|||
 | 
				
			|||
            // initial bypass chances
 | 
				
			|||
            if (!opt.enabled) { | 
				
			|||
                return bypass(); | 
				
			|||
            } | 
				
			|||
            if ( | 
				
			|||
                req.headers["x-apicache-bypass"] || | 
				
			|||
        req.headers["x-apicache-force-fetch"] || | 
				
			|||
        (opt.respectCacheControl && req.headers["cache-control"] == "no-cache") | 
				
			|||
            ) { | 
				
			|||
                return bypass(); | 
				
			|||
            } | 
				
			|||
 | 
				
			|||
            // REMOVED IN 0.11.1 TO CORRECT MIDDLEWARE TOGGLE EXECUTE ORDER
 | 
				
			|||
            // if (typeof middlewareToggle === 'function') {
 | 
				
			|||
            //   if (!middlewareToggle(req, res)) return bypass()
 | 
				
			|||
            // } else if (middlewareToggle !== undefined && !middlewareToggle) {
 | 
				
			|||
            //   return bypass()
 | 
				
			|||
            // }
 | 
				
			|||
 | 
				
			|||
            // embed timer
 | 
				
			|||
            req.apicacheTimer = new Date(); | 
				
			|||
 | 
				
			|||
            // In Express 4.x the url is ambigious based on where a router is mounted.  originalUrl will give the full Url
 | 
				
			|||
            let key = req.originalUrl || req.url; | 
				
			|||
 | 
				
			|||
            // Remove querystring from key if jsonp option is enabled
 | 
				
			|||
            if (opt.jsonp) { | 
				
			|||
                key = url.parse(key).pathname; | 
				
			|||
            } | 
				
			|||
 | 
				
			|||
            // add appendKey (either custom function or response path)
 | 
				
			|||
            if (typeof opt.appendKey === "function") { | 
				
			|||
                key += "$$appendKey=" + opt.appendKey(req, res); | 
				
			|||
            } else if (opt.appendKey.length > 0) { | 
				
			|||
                let appendKey = req; | 
				
			|||
 | 
				
			|||
                for (let i = 0; i < opt.appendKey.length; i++) { | 
				
			|||
                    appendKey = appendKey[opt.appendKey[i]]; | 
				
			|||
                } | 
				
			|||
                key += "$$appendKey=" + appendKey; | 
				
			|||
            } | 
				
			|||
 | 
				
			|||
            // attempt cache hit
 | 
				
			|||
            let redis = opt.redisClient; | 
				
			|||
            let cached = !redis ? memCache.getValue(key) : null; | 
				
			|||
 | 
				
			|||
            // send if cache hit from memory-cache
 | 
				
			|||
            if (cached) { | 
				
			|||
                let elapsed = new Date() - req.apicacheTimer; | 
				
			|||
                debug("sending cached (memory-cache) version of", key, logDuration(elapsed)); | 
				
			|||
 | 
				
			|||
                perf.hit(key); | 
				
			|||
                return sendCachedResponse(req, res, cached, middlewareToggle, next, duration); | 
				
			|||
            } | 
				
			|||
 | 
				
			|||
            // send if cache hit from redis
 | 
				
			|||
            if (redis && redis.connected) { | 
				
			|||
                try { | 
				
			|||
                    redis.hgetall(key, function (err, obj) { | 
				
			|||
                        if (!err && obj && obj.response) { | 
				
			|||
                            let elapsed = new Date() - req.apicacheTimer; | 
				
			|||
                            debug("sending cached (redis) version of", key, logDuration(elapsed)); | 
				
			|||
 | 
				
			|||
                            perf.hit(key); | 
				
			|||
                            return sendCachedResponse( | 
				
			|||
                                req, | 
				
			|||
                                res, | 
				
			|||
                                JSON.parse(obj.response), | 
				
			|||
                                middlewareToggle, | 
				
			|||
                                next, | 
				
			|||
                                duration | 
				
			|||
                            ); | 
				
			|||
                        } else { | 
				
			|||
                            perf.miss(key); | 
				
			|||
                            return makeResponseCacheable( | 
				
			|||
                                req, | 
				
			|||
                                res, | 
				
			|||
                                next, | 
				
			|||
                                key, | 
				
			|||
                                duration, | 
				
			|||
                                strDuration, | 
				
			|||
                                middlewareToggle | 
				
			|||
                            ); | 
				
			|||
                        } | 
				
			|||
                    }); | 
				
			|||
                } catch (err) { | 
				
			|||
                    // bypass redis on error
 | 
				
			|||
                    perf.miss(key); | 
				
			|||
                    return makeResponseCacheable(req, res, next, key, duration, strDuration, middlewareToggle); | 
				
			|||
                } | 
				
			|||
            } else { | 
				
			|||
                perf.miss(key); | 
				
			|||
                return makeResponseCacheable(req, res, next, key, duration, strDuration, middlewareToggle); | 
				
			|||
            } | 
				
			|||
        }; | 
				
			|||
 | 
				
			|||
        cache.options = options; | 
				
			|||
 | 
				
			|||
        return cache; | 
				
			|||
    }; | 
				
			|||
 | 
				
			|||
    this.options = function (options) { | 
				
			|||
        if (options) { | 
				
			|||
            Object.assign(globalOptions, options); | 
				
			|||
            syncOptions(); | 
				
			|||
 | 
				
			|||
            if ("defaultDuration" in options) { | 
				
			|||
                // Convert the default duration to a number in milliseconds (if needed)
 | 
				
			|||
                globalOptions.defaultDuration = parseDuration(globalOptions.defaultDuration, 3600000); | 
				
			|||
            } | 
				
			|||
 | 
				
			|||
            if (globalOptions.trackPerformance) { | 
				
			|||
                debug("WARNING: using trackPerformance flag can cause high memory usage!"); | 
				
			|||
            } | 
				
			|||
 | 
				
			|||
            return this; | 
				
			|||
        } else { | 
				
			|||
            return globalOptions; | 
				
			|||
        } | 
				
			|||
    }; | 
				
			|||
 | 
				
			|||
    this.resetIndex = function () { | 
				
			|||
        index = { | 
				
			|||
            all: [], | 
				
			|||
            groups: {}, | 
				
			|||
        }; | 
				
			|||
    }; | 
				
			|||
 | 
				
			|||
    this.newInstance = function (config) { | 
				
			|||
        let instance = new ApiCache(); | 
				
			|||
 | 
				
			|||
        if (config) { | 
				
			|||
            instance.options(config); | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        return instance; | 
				
			|||
    }; | 
				
			|||
 | 
				
			|||
    this.clone = function () { | 
				
			|||
        return this.newInstance(this.options()); | 
				
			|||
    }; | 
				
			|||
 | 
				
			|||
    // initialize index
 | 
				
			|||
    this.resetIndex(); | 
				
			|||
} | 
				
			|||
 | 
				
			|||
module.exports = new ApiCache(); | 
				
			|||
@ -0,0 +1,14 @@ | 
				
			|||
const apicache = require("./apicache"); | 
				
			|||
 | 
				
			|||
apicache.options({ | 
				
			|||
    headerBlacklist: [ | 
				
			|||
        "cache-control" | 
				
			|||
    ], | 
				
			|||
    headers: { | 
				
			|||
        // Disable client side cache, only server side cache.
 | 
				
			|||
        // BUG! Not working for the second request
 | 
				
			|||
        "cache-control": "no-cache", | 
				
			|||
    }, | 
				
			|||
}); | 
				
			|||
 | 
				
			|||
module.exports = apicache; | 
				
			|||
@ -0,0 +1,59 @@ | 
				
			|||
function MemoryCache() { | 
				
			|||
    this.cache = {}; | 
				
			|||
    this.size = 0; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
MemoryCache.prototype.add = function (key, value, time, timeoutCallback) { | 
				
			|||
    let old = this.cache[key]; | 
				
			|||
    let instance = this; | 
				
			|||
 | 
				
			|||
    let entry = { | 
				
			|||
        value: value, | 
				
			|||
        expire: time + Date.now(), | 
				
			|||
        timeout: setTimeout(function () { | 
				
			|||
            instance.delete(key); | 
				
			|||
            return timeoutCallback && typeof timeoutCallback === "function" && timeoutCallback(value, key); | 
				
			|||
        }, time) | 
				
			|||
    }; | 
				
			|||
 | 
				
			|||
    this.cache[key] = entry; | 
				
			|||
    this.size = Object.keys(this.cache).length; | 
				
			|||
 | 
				
			|||
    return entry; | 
				
			|||
}; | 
				
			|||
 | 
				
			|||
MemoryCache.prototype.delete = function (key) { | 
				
			|||
    let entry = this.cache[key]; | 
				
			|||
 | 
				
			|||
    if (entry) { | 
				
			|||
        clearTimeout(entry.timeout); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    delete this.cache[key]; | 
				
			|||
 | 
				
			|||
    this.size = Object.keys(this.cache).length; | 
				
			|||
 | 
				
			|||
    return null; | 
				
			|||
}; | 
				
			|||
 | 
				
			|||
MemoryCache.prototype.get = function (key) { | 
				
			|||
    let entry = this.cache[key]; | 
				
			|||
 | 
				
			|||
    return entry; | 
				
			|||
}; | 
				
			|||
 | 
				
			|||
MemoryCache.prototype.getValue = function (key) { | 
				
			|||
    let entry = this.get(key); | 
				
			|||
 | 
				
			|||
    return entry && entry.value; | 
				
			|||
}; | 
				
			|||
 | 
				
			|||
MemoryCache.prototype.clear = function () { | 
				
			|||
    Object.keys(this.cache).forEach(function (key) { | 
				
			|||
        this.delete(key); | 
				
			|||
    }, this); | 
				
			|||
 | 
				
			|||
    return true; | 
				
			|||
}; | 
				
			|||
 | 
				
			|||
module.exports = MemoryCache; | 
				
			|||
@ -0,0 +1,26 @@ | 
				
			|||
const NotificationProvider = require("./notification-provider"); | 
				
			|||
const child_process = require("child_process"); | 
				
			|||
 | 
				
			|||
class Apprise extends NotificationProvider { | 
				
			|||
 | 
				
			|||
    name = "apprise"; | 
				
			|||
 | 
				
			|||
    async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { | 
				
			|||
        let s = child_process.spawnSync("apprise", [ "-vv", "-b", msg, notification.appriseURL]) | 
				
			|||
 | 
				
			|||
        let output = (s.stdout) ? s.stdout.toString() : "ERROR: maybe apprise not found"; | 
				
			|||
 | 
				
			|||
        if (output) { | 
				
			|||
 | 
				
			|||
            if (! output.includes("ERROR")) { | 
				
			|||
                return "Sent Successfully"; | 
				
			|||
            } | 
				
			|||
 | 
				
			|||
            throw new Error(output) | 
				
			|||
        } else { | 
				
			|||
            return "No output from apprise"; | 
				
			|||
        } | 
				
			|||
    } | 
				
			|||
} | 
				
			|||
 | 
				
			|||
module.exports = Apprise; | 
				
			|||
@ -0,0 +1,115 @@ | 
				
			|||
const NotificationProvider = require("./notification-provider"); | 
				
			|||
const axios = require("axios"); | 
				
			|||
const { DOWN, UP } = require("../../src/util"); | 
				
			|||
 | 
				
			|||
class Discord extends NotificationProvider { | 
				
			|||
 | 
				
			|||
    name = "discord"; | 
				
			|||
 | 
				
			|||
    async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { | 
				
			|||
        let okMsg = "Sent Successfully."; | 
				
			|||
 | 
				
			|||
        try { | 
				
			|||
            const discordDisplayName = notification.discordUsername || "Uptime Kuma"; | 
				
			|||
 | 
				
			|||
            // If heartbeatJSON is null, assume we're testing.
 | 
				
			|||
            if (heartbeatJSON == null) { | 
				
			|||
                let discordtestdata = { | 
				
			|||
                    username: discordDisplayName, | 
				
			|||
                    content: msg, | 
				
			|||
                } | 
				
			|||
                await axios.post(notification.discordWebhookUrl, discordtestdata) | 
				
			|||
                return okMsg; | 
				
			|||
            } | 
				
			|||
 | 
				
			|||
            let url; | 
				
			|||
 | 
				
			|||
            if (monitorJSON["type"] === "port") { | 
				
			|||
                url = monitorJSON["hostname"]; | 
				
			|||
                if (monitorJSON["port"]) { | 
				
			|||
                    url += ":" + monitorJSON["port"]; | 
				
			|||
                } | 
				
			|||
 | 
				
			|||
            } else { | 
				
			|||
                url = monitorJSON["url"]; | 
				
			|||
            } | 
				
			|||
 | 
				
			|||
            // If heartbeatJSON is not null, we go into the normal alerting loop.
 | 
				
			|||
            if (heartbeatJSON["status"] == DOWN) { | 
				
			|||
                let discorddowndata = { | 
				
			|||
                    username: discordDisplayName, | 
				
			|||
                    embeds: [{ | 
				
			|||
                        title: "❌ Your service " + monitorJSON["name"] + " went down. ❌", | 
				
			|||
                        color: 16711680, | 
				
			|||
                        timestamp: heartbeatJSON["time"], | 
				
			|||
                        fields: [ | 
				
			|||
                            { | 
				
			|||
                                name: "Service Name", | 
				
			|||
                                value: monitorJSON["name"], | 
				
			|||
                            }, | 
				
			|||
                            { | 
				
			|||
                                name: "Service URL", | 
				
			|||
                                value: url, | 
				
			|||
                            }, | 
				
			|||
                            { | 
				
			|||
                                name: "Time (UTC)", | 
				
			|||
                                value: heartbeatJSON["time"], | 
				
			|||
                            }, | 
				
			|||
                            { | 
				
			|||
                                name: "Error", | 
				
			|||
                                value: heartbeatJSON["msg"], | 
				
			|||
                            }, | 
				
			|||
                        ], | 
				
			|||
                    }], | 
				
			|||
                } | 
				
			|||
 | 
				
			|||
                if (notification.discordPrefixMessage) { | 
				
			|||
                    discorddowndata.content = notification.discordPrefixMessage; | 
				
			|||
                } | 
				
			|||
 | 
				
			|||
                await axios.post(notification.discordWebhookUrl, discorddowndata) | 
				
			|||
                return okMsg; | 
				
			|||
 | 
				
			|||
            } else if (heartbeatJSON["status"] == UP) { | 
				
			|||
                let discordupdata = { | 
				
			|||
                    username: discordDisplayName, | 
				
			|||
                    embeds: [{ | 
				
			|||
                        title: "✅ Your service " + monitorJSON["name"] + " is up! ✅", | 
				
			|||
                        color: 65280, | 
				
			|||
                        timestamp: heartbeatJSON["time"], | 
				
			|||
                        fields: [ | 
				
			|||
                            { | 
				
			|||
                                name: "Service Name", | 
				
			|||
                                value: monitorJSON["name"], | 
				
			|||
                            }, | 
				
			|||
                            { | 
				
			|||
                                name: "Service URL", | 
				
			|||
                                value: url.startsWith("http") ? "[Visit Service](" + url + ")" : url, | 
				
			|||
                            }, | 
				
			|||
                            { | 
				
			|||
                                name: "Time (UTC)", | 
				
			|||
                                value: heartbeatJSON["time"], | 
				
			|||
                            }, | 
				
			|||
                            { | 
				
			|||
                                name: "Ping", | 
				
			|||
                                value: heartbeatJSON["ping"] + "ms", | 
				
			|||
                            }, | 
				
			|||
                        ], | 
				
			|||
                    }], | 
				
			|||
                } | 
				
			|||
 | 
				
			|||
                if (notification.discordPrefixMessage) { | 
				
			|||
                    discordupdata.content = notification.discordPrefixMessage; | 
				
			|||
                } | 
				
			|||
 | 
				
			|||
                await axios.post(notification.discordWebhookUrl, discordupdata) | 
				
			|||
                return okMsg; | 
				
			|||
            } | 
				
			|||
        } catch (error) { | 
				
			|||
            this.throwGeneralAxiosError(error) | 
				
			|||
        } | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
} | 
				
			|||
 | 
				
			|||
module.exports = Discord; | 
				
			|||
@ -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,28 @@ | 
				
			|||
const NotificationProvider = require("./notification-provider"); | 
				
			|||
const axios = require("axios"); | 
				
			|||
 | 
				
			|||
class Gotify extends NotificationProvider { | 
				
			|||
 | 
				
			|||
    name = "gotify"; | 
				
			|||
 | 
				
			|||
    async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { | 
				
			|||
        let okMsg = "Sent Successfully."; | 
				
			|||
        try { | 
				
			|||
            if (notification.gotifyserverurl && notification.gotifyserverurl.endsWith("/")) { | 
				
			|||
                notification.gotifyserverurl = notification.gotifyserverurl.slice(0, -1); | 
				
			|||
            } | 
				
			|||
            await axios.post(`${notification.gotifyserverurl}/message?token=${notification.gotifyapplicationToken}`, { | 
				
			|||
                "message": msg, | 
				
			|||
                "priority": notification.gotifyPriority || 8, | 
				
			|||
                "title": "Uptime-Kuma", | 
				
			|||
            }) | 
				
			|||
 | 
				
			|||
            return okMsg; | 
				
			|||
 | 
				
			|||
        } catch (error) { | 
				
			|||
            this.throwGeneralAxiosError(error); | 
				
			|||
        } | 
				
			|||
    } | 
				
			|||
} | 
				
			|||
 | 
				
			|||
module.exports = Gotify; | 
				
			|||
@ -0,0 +1,60 @@ | 
				
			|||
const NotificationProvider = require("./notification-provider"); | 
				
			|||
const axios = require("axios"); | 
				
			|||
const { DOWN, UP } = require("../../src/util"); | 
				
			|||
 | 
				
			|||
class Line extends NotificationProvider { | 
				
			|||
 | 
				
			|||
    name = "line"; | 
				
			|||
 | 
				
			|||
    async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { | 
				
			|||
        let okMsg = "Sent Successfully."; | 
				
			|||
        try { | 
				
			|||
            let lineAPIUrl = "https://api.line.me/v2/bot/message/push"; | 
				
			|||
            let config = { | 
				
			|||
                headers: { | 
				
			|||
                    "Content-Type": "application/json", | 
				
			|||
                    "Authorization": "Bearer " + notification.lineChannelAccessToken | 
				
			|||
                } | 
				
			|||
            }; | 
				
			|||
            if (heartbeatJSON == null) { | 
				
			|||
                let testMessage = { | 
				
			|||
                    "to": notification.lineUserID, | 
				
			|||
                    "messages": [ | 
				
			|||
                        { | 
				
			|||
                            "type": "text", | 
				
			|||
                            "text": "Test Successful!" | 
				
			|||
                        } | 
				
			|||
                    ] | 
				
			|||
                } | 
				
			|||
                await axios.post(lineAPIUrl, testMessage, config) | 
				
			|||
            } else if (heartbeatJSON["status"] == DOWN) { | 
				
			|||
                let downMessage = { | 
				
			|||
                    "to": notification.lineUserID, | 
				
			|||
                    "messages": [ | 
				
			|||
                        { | 
				
			|||
                            "type": "text", | 
				
			|||
                            "text": "UptimeKuma Alert: [🔴 Down]\n" + "Name: " + monitorJSON["name"] + " \n" + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"] | 
				
			|||
                        } | 
				
			|||
                    ] | 
				
			|||
                } | 
				
			|||
                await axios.post(lineAPIUrl, downMessage, config) | 
				
			|||
            } else if (heartbeatJSON["status"] == UP) { | 
				
			|||
                let upMessage = { | 
				
			|||
                    "to": notification.lineUserID, | 
				
			|||
                    "messages": [ | 
				
			|||
                        { | 
				
			|||
                            "type": "text", | 
				
			|||
                            "text": "UptimeKuma Alert: [✅ Up]\n" + "Name: " + monitorJSON["name"] + " \n" + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"] | 
				
			|||
                        } | 
				
			|||
                    ] | 
				
			|||
                } | 
				
			|||
                await axios.post(lineAPIUrl, upMessage, config) | 
				
			|||
            } | 
				
			|||
            return okMsg; | 
				
			|||
        } catch (error) { | 
				
			|||
            this.throwGeneralAxiosError(error) | 
				
			|||
        } | 
				
			|||
    } | 
				
			|||
} | 
				
			|||
 | 
				
			|||
module.exports = Line; | 
				
			|||
@ -0,0 +1,48 @@ | 
				
			|||
const NotificationProvider = require("./notification-provider"); | 
				
			|||
const axios = require("axios"); | 
				
			|||
const { DOWN, UP } = require("../../src/util"); | 
				
			|||
 | 
				
			|||
class LunaSea extends NotificationProvider { | 
				
			|||
 | 
				
			|||
    name = "lunasea"; | 
				
			|||
 | 
				
			|||
    async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { | 
				
			|||
        let okMsg = "Sent Successfully."; | 
				
			|||
        let lunaseadevice = "https://notify.lunasea.app/v1/custom/device/" + notification.lunaseaDevice | 
				
			|||
 | 
				
			|||
        try { | 
				
			|||
            if (heartbeatJSON == null) { | 
				
			|||
                let testdata = { | 
				
			|||
                    "title": "Uptime Kuma Alert", | 
				
			|||
                    "body": "Testing Successful.", | 
				
			|||
                } | 
				
			|||
                await axios.post(lunaseadevice, testdata) | 
				
			|||
                return okMsg; | 
				
			|||
            } | 
				
			|||
 | 
				
			|||
            if (heartbeatJSON["status"] == DOWN) { | 
				
			|||
                let downdata = { | 
				
			|||
                    "title": "UptimeKuma Alert: " + monitorJSON["name"], | 
				
			|||
                    "body": "[🔴 Down] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"], | 
				
			|||
                } | 
				
			|||
                await axios.post(lunaseadevice, downdata) | 
				
			|||
                return okMsg; | 
				
			|||
            } | 
				
			|||
 | 
				
			|||
            if (heartbeatJSON["status"] == UP) { | 
				
			|||
                let updata = { | 
				
			|||
                    "title": "UptimeKuma Alert: " + monitorJSON["name"], | 
				
			|||
                    "body": "[✅ Up] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"], | 
				
			|||
                } | 
				
			|||
                await axios.post(lunaseadevice, updata) | 
				
			|||
                return okMsg; | 
				
			|||
            } | 
				
			|||
 | 
				
			|||
        } catch (error) { | 
				
			|||
            this.throwGeneralAxiosError(error) | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
    } | 
				
			|||
} | 
				
			|||
 | 
				
			|||
module.exports = LunaSea; | 
				
			|||
@ -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,123 @@ | 
				
			|||
const NotificationProvider = require("./notification-provider"); | 
				
			|||
const axios = require("axios"); | 
				
			|||
const { DOWN, UP } = require("../../src/util"); | 
				
			|||
 | 
				
			|||
class Mattermost extends NotificationProvider { | 
				
			|||
 | 
				
			|||
    name = "mattermost"; | 
				
			|||
 | 
				
			|||
    async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { | 
				
			|||
        let okMsg = "Sent Successfully."; | 
				
			|||
        try { | 
				
			|||
            const mattermostUserName = notification.mattermostusername || "Uptime Kuma"; | 
				
			|||
            // If heartbeatJSON is null, assume we're testing.
 | 
				
			|||
            if (heartbeatJSON == null) { | 
				
			|||
                let mattermostTestData = { | 
				
			|||
                    username: mattermostUserName, | 
				
			|||
                    text: msg, | 
				
			|||
                } | 
				
			|||
                await axios.post(notification.mattermostWebhookUrl, mattermostTestData) | 
				
			|||
                return okMsg; | 
				
			|||
            } | 
				
			|||
 | 
				
			|||
            const mattermostChannel = notification.mattermostchannel; | 
				
			|||
            const mattermostIconEmoji = notification.mattermosticonemo; | 
				
			|||
            const mattermostIconUrl = notification.mattermosticonurl; | 
				
			|||
 | 
				
			|||
            if (heartbeatJSON["status"] == DOWN) { | 
				
			|||
                let mattermostdowndata = { | 
				
			|||
                    username: mattermostUserName, | 
				
			|||
                    text: "Uptime Kuma Alert", | 
				
			|||
                    channel: mattermostChannel, | 
				
			|||
                    icon_emoji: mattermostIconEmoji, | 
				
			|||
                    icon_url: mattermostIconUrl, | 
				
			|||
                    attachments: [ | 
				
			|||
                        { | 
				
			|||
                            fallback: | 
				
			|||
                                "Your " + | 
				
			|||
                                monitorJSON["name"] + | 
				
			|||
                                " service went down.", | 
				
			|||
                            color: "#FF0000", | 
				
			|||
                            title: | 
				
			|||
                                "❌ " + | 
				
			|||
                                monitorJSON["name"] + | 
				
			|||
                                " service went down. ❌", | 
				
			|||
                            title_link: monitorJSON["url"], | 
				
			|||
                            fields: [ | 
				
			|||
                                { | 
				
			|||
                                    short: true, | 
				
			|||
                                    title: "Service Name", | 
				
			|||
                                    value: monitorJSON["name"], | 
				
			|||
                                }, | 
				
			|||
                                { | 
				
			|||
                                    short: true, | 
				
			|||
                                    title: "Time (UTC)", | 
				
			|||
                                    value: heartbeatJSON["time"], | 
				
			|||
                                }, | 
				
			|||
                                { | 
				
			|||
                                    short: false, | 
				
			|||
                                    title: "Error", | 
				
			|||
                                    value: heartbeatJSON["msg"], | 
				
			|||
                                }, | 
				
			|||
                            ], | 
				
			|||
                        }, | 
				
			|||
                    ], | 
				
			|||
                }; | 
				
			|||
                await axios.post( | 
				
			|||
                    notification.mattermostWebhookUrl, | 
				
			|||
                    mattermostdowndata | 
				
			|||
                ); | 
				
			|||
                return okMsg; | 
				
			|||
            } else if (heartbeatJSON["status"] == UP) { | 
				
			|||
                let mattermostupdata = { | 
				
			|||
                    username: mattermostUserName, | 
				
			|||
                    text: "Uptime Kuma Alert", | 
				
			|||
                    channel: mattermostChannel, | 
				
			|||
                    icon_emoji: mattermostIconEmoji, | 
				
			|||
                    icon_url: mattermostIconUrl, | 
				
			|||
                    attachments: [ | 
				
			|||
                        { | 
				
			|||
                            fallback: | 
				
			|||
                                "Your " + | 
				
			|||
                                monitorJSON["name"] + | 
				
			|||
                                " service went up!", | 
				
			|||
                            color: "#32CD32", | 
				
			|||
                            title: | 
				
			|||
                                "✅ " + | 
				
			|||
                                monitorJSON["name"] + | 
				
			|||
                                " service went up! ✅", | 
				
			|||
                            title_link: monitorJSON["url"], | 
				
			|||
                            fields: [ | 
				
			|||
                                { | 
				
			|||
                                    short: true, | 
				
			|||
                                    title: "Service Name", | 
				
			|||
                                    value: monitorJSON["name"], | 
				
			|||
                                }, | 
				
			|||
                                { | 
				
			|||
                                    short: true, | 
				
			|||
                                    title: "Time (UTC)", | 
				
			|||
                                    value: heartbeatJSON["time"], | 
				
			|||
                                }, | 
				
			|||
                                { | 
				
			|||
                                    short: false, | 
				
			|||
                                    title: "Ping", | 
				
			|||
                                    value: heartbeatJSON["ping"] + "ms", | 
				
			|||
                                }, | 
				
			|||
                            ], | 
				
			|||
                        }, | 
				
			|||
                    ], | 
				
			|||
                }; | 
				
			|||
                await axios.post( | 
				
			|||
                    notification.mattermostWebhookUrl, | 
				
			|||
                    mattermostupdata | 
				
			|||
                ); | 
				
			|||
                return okMsg; | 
				
			|||
            } | 
				
			|||
        } catch (error) { | 
				
			|||
            this.throwGeneralAxiosError(error); | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
    } | 
				
			|||
} | 
				
			|||
 | 
				
			|||
module.exports = Mattermost; | 
				
			|||
@ -0,0 +1,36 @@ | 
				
			|||
class NotificationProvider { | 
				
			|||
 | 
				
			|||
    /** | 
				
			|||
     * Notification Provider Name | 
				
			|||
     * @type string | 
				
			|||
     */ | 
				
			|||
    name = undefined; | 
				
			|||
 | 
				
			|||
    /** | 
				
			|||
     * @param notification : BeanModel | 
				
			|||
     * @param msg : string General Message | 
				
			|||
     * @param monitorJSON : object Monitor details (For Up/Down only) | 
				
			|||
     * @param heartbeatJSON : object Heartbeat details (For Up/Down only) | 
				
			|||
     * @returns {Promise<string>} Return Successful Message | 
				
			|||
     * Throw Error with fail msg | 
				
			|||
     */ | 
				
			|||
    async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { | 
				
			|||
        throw new Error("Have to override Notification.send(...)"); | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    throwGeneralAxiosError(error) { | 
				
			|||
        let msg = "Error: " + error + " "; | 
				
			|||
 | 
				
			|||
        if (error.response && error.response.data) { | 
				
			|||
            if (typeof error.response.data === "string") { | 
				
			|||
                msg += error.response.data; | 
				
			|||
            } else { | 
				
			|||
                msg += JSON.stringify(error.response.data) | 
				
			|||
            } | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        throw new Error(msg) | 
				
			|||
    } | 
				
			|||
} | 
				
			|||
 | 
				
			|||
module.exports = NotificationProvider; | 
				
			|||
@ -0,0 +1,64 @@ | 
				
			|||
const NotificationProvider = require("./notification-provider"); | 
				
			|||
const axios = require("axios"); | 
				
			|||
 | 
				
			|||
class Octopush extends NotificationProvider { | 
				
			|||
 | 
				
			|||
    name = "octopush"; | 
				
			|||
 | 
				
			|||
    async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { | 
				
			|||
        let okMsg = "Sent Successfully."; | 
				
			|||
 | 
				
			|||
        try { | 
				
			|||
        // Default - V2
 | 
				
			|||
            if (notification.octopushVersion == 2 || !notification.octopushVersion) { | 
				
			|||
                let config = { | 
				
			|||
                    headers: { | 
				
			|||
                        "api-key": notification.octopushAPIKey, | 
				
			|||
                        "api-login": notification.octopushLogin, | 
				
			|||
                        "cache-control": "no-cache" | 
				
			|||
                    } | 
				
			|||
                }; | 
				
			|||
                let data = { | 
				
			|||
                    "recipients": [ | 
				
			|||
                        { | 
				
			|||
                            "phone_number": notification.octopushPhoneNumber | 
				
			|||
                        } | 
				
			|||
                    ], | 
				
			|||
                    //octopush not supporting non ascii char
 | 
				
			|||
                    "text": msg.replace(/[^\x00-\x7F]/g, ""), | 
				
			|||
                    "type": notification.octopushSMSType, | 
				
			|||
                    "purpose": "alert", | 
				
			|||
                    "sender": notification.octopushSenderName | 
				
			|||
                }; | 
				
			|||
                await axios.post("https://api.octopush.com/v1/public/sms-campaign/send", data, config) | 
				
			|||
            } else if (notification.octopushVersion == 1) { | 
				
			|||
                let data = { | 
				
			|||
                    "user_login": notification.octopushDMLogin, | 
				
			|||
                    "api_key": notification.octopushDMAPIKey, | 
				
			|||
                    "sms_recipients": notification.octopushDMPhoneNumber, | 
				
			|||
                    "sms_sender": notification.octopushDMSenderName, | 
				
			|||
                    "sms_type": (notification.octopushDMSMSType == "sms_premium") ? "FR" : "XXX", | 
				
			|||
                    "transactional": "1", | 
				
			|||
                    //octopush not supporting non ascii char
 | 
				
			|||
                    "sms_text": msg.replace(/[^\x00-\x7F]/g, ""), | 
				
			|||
                }; | 
				
			|||
 | 
				
			|||
                let config = { | 
				
			|||
                    headers: { | 
				
			|||
                        "cache-control": "no-cache" | 
				
			|||
                    }, | 
				
			|||
                    params: data | 
				
			|||
                }; | 
				
			|||
                await axios.post("https://www.octopush-dm.com/api/sms/json", {}, config) | 
				
			|||
            } else { | 
				
			|||
                throw new Error("Unknown Octopush version!"); | 
				
			|||
            } | 
				
			|||
 | 
				
			|||
            return okMsg; | 
				
			|||
        } catch (error) { | 
				
			|||
            this.throwGeneralAxiosError(error); | 
				
			|||
        } | 
				
			|||
    } | 
				
			|||
} | 
				
			|||
 | 
				
			|||
module.exports = Octopush; | 
				
			|||
@ -0,0 +1,41 @@ | 
				
			|||
const NotificationProvider = require("./notification-provider"); | 
				
			|||
const axios = require("axios"); | 
				
			|||
 | 
				
			|||
class PromoSMS extends NotificationProvider { | 
				
			|||
 | 
				
			|||
    name = "promosms"; | 
				
			|||
 | 
				
			|||
    async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { | 
				
			|||
        let okMsg = "Sent Successfully."; | 
				
			|||
 | 
				
			|||
        try { | 
				
			|||
            let config = { | 
				
			|||
                headers: { | 
				
			|||
                    "Content-Type": "application/json", | 
				
			|||
                    "Authorization": "Basic " + Buffer.from(notification.promosmsLogin + ":" + notification.promosmsPassword).toString('base64'), | 
				
			|||
                    "Accept": "text/json", | 
				
			|||
                } | 
				
			|||
            }; | 
				
			|||
            let data = { | 
				
			|||
                "recipients": [ notification.promosmsPhoneNumber ], | 
				
			|||
                //Lets remove non ascii char
 | 
				
			|||
                "text": msg.replace(/[^\x00-\x7F]/g, ""), | 
				
			|||
                "type": Number(notification.promosmsSMSType), | 
				
			|||
                "sender": notification.promosmsSenderName | 
				
			|||
            }; | 
				
			|||
 | 
				
			|||
            let resp = await axios.post("https://promosms.com/api/rest/v3_2/sms", data, config); | 
				
			|||
 | 
				
			|||
            if (resp.data.response.status !== 0) { | 
				
			|||
                let error = "Something gone wrong. Api returned " + resp.data.response.status + "."; | 
				
			|||
                this.throwGeneralAxiosError(error); | 
				
			|||
            } | 
				
			|||
             | 
				
			|||
            return okMsg; | 
				
			|||
        } catch (error) { | 
				
			|||
            this.throwGeneralAxiosError(error); | 
				
			|||
        } | 
				
			|||
    } | 
				
			|||
} | 
				
			|||
 | 
				
			|||
module.exports = PromoSMS; | 
				
			|||
@ -0,0 +1,50 @@ | 
				
			|||
const NotificationProvider = require("./notification-provider"); | 
				
			|||
const axios = require("axios"); | 
				
			|||
 | 
				
			|||
const { DOWN, UP } = require("../../src/util"); | 
				
			|||
 | 
				
			|||
class Pushbullet extends NotificationProvider { | 
				
			|||
 | 
				
			|||
    name = "pushbullet"; | 
				
			|||
 | 
				
			|||
    async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { | 
				
			|||
        let okMsg = "Sent Successfully."; | 
				
			|||
 | 
				
			|||
        try { | 
				
			|||
            let pushbulletUrl = "https://api.pushbullet.com/v2/pushes"; | 
				
			|||
            let config = { | 
				
			|||
                headers: { | 
				
			|||
                    "Access-Token": notification.pushbulletAccessToken, | 
				
			|||
                    "Content-Type": "application/json" | 
				
			|||
                } | 
				
			|||
            }; | 
				
			|||
            if (heartbeatJSON == null) { | 
				
			|||
                let testdata = { | 
				
			|||
                    "type": "note", | 
				
			|||
                    "title": "Uptime Kuma Alert", | 
				
			|||
                    "body": "Testing Successful.", | 
				
			|||
                } | 
				
			|||
                await axios.post(pushbulletUrl, testdata, config) | 
				
			|||
            } else if (heartbeatJSON["status"] == DOWN) { | 
				
			|||
                let downdata = { | 
				
			|||
                    "type": "note", | 
				
			|||
                    "title": "UptimeKuma Alert: " + monitorJSON["name"], | 
				
			|||
                    "body": "[🔴 Down] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"], | 
				
			|||
                } | 
				
			|||
                await axios.post(pushbulletUrl, downdata, config) | 
				
			|||
            } else if (heartbeatJSON["status"] == UP) { | 
				
			|||
                let updata = { | 
				
			|||
                    "type": "note", | 
				
			|||
                    "title": "UptimeKuma Alert: " + monitorJSON["name"], | 
				
			|||
                    "body": "[✅ Up] " + heartbeatJSON["msg"] + "\nTime (UTC): " + heartbeatJSON["time"], | 
				
			|||
                } | 
				
			|||
                await axios.post(pushbulletUrl, updata, config) | 
				
			|||
            } | 
				
			|||
            return okMsg; | 
				
			|||
        } catch (error) { | 
				
			|||
            this.throwGeneralAxiosError(error) | 
				
			|||
        } | 
				
			|||
    } | 
				
			|||
} | 
				
			|||
 | 
				
			|||
module.exports = Pushbullet; | 
				
			|||
@ -0,0 +1,49 @@ | 
				
			|||
const NotificationProvider = require("./notification-provider"); | 
				
			|||
const axios = require("axios"); | 
				
			|||
 | 
				
			|||
class Pushover extends NotificationProvider { | 
				
			|||
 | 
				
			|||
    name = "pushover"; | 
				
			|||
 | 
				
			|||
    async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { | 
				
			|||
        let okMsg = "Sent Successfully."; | 
				
			|||
        let pushoverlink = "https://api.pushover.net/1/messages.json" | 
				
			|||
 | 
				
			|||
        try { | 
				
			|||
            if (heartbeatJSON == null) { | 
				
			|||
                let data = { | 
				
			|||
                    "message": "<b>Uptime Kuma Pushover testing successful.</b>", | 
				
			|||
                    "user": notification.pushoveruserkey, | 
				
			|||
                    "token": notification.pushoverapptoken, | 
				
			|||
                    "sound": notification.pushoversounds, | 
				
			|||
                    "priority": notification.pushoverpriority, | 
				
			|||
                    "title": notification.pushovertitle, | 
				
			|||
                    "retry": "30", | 
				
			|||
                    "expire": "3600", | 
				
			|||
                    "html": 1, | 
				
			|||
                } | 
				
			|||
                await axios.post(pushoverlink, data) | 
				
			|||
                return okMsg; | 
				
			|||
            } | 
				
			|||
 | 
				
			|||
            let data = { | 
				
			|||
                "message": "<b>Uptime Kuma Alert</b>\n\n<b>Message</b>:" + msg + "\n<b>Time (UTC)</b>:" + heartbeatJSON["time"], | 
				
			|||
                "user": notification.pushoveruserkey, | 
				
			|||
                "token": notification.pushoverapptoken, | 
				
			|||
                "sound": notification.pushoversounds, | 
				
			|||
                "priority": notification.pushoverpriority, | 
				
			|||
                "title": notification.pushovertitle, | 
				
			|||
                "retry": "30", | 
				
			|||
                "expire": "3600", | 
				
			|||
                "html": 1, | 
				
			|||
            } | 
				
			|||
            await axios.post(pushoverlink, data) | 
				
			|||
            return okMsg; | 
				
			|||
        } catch (error) { | 
				
			|||
            this.throwGeneralAxiosError(error) | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
    } | 
				
			|||
} | 
				
			|||
 | 
				
			|||
module.exports = Pushover; | 
				
			|||
@ -0,0 +1,30 @@ | 
				
			|||
const NotificationProvider = require("./notification-provider"); | 
				
			|||
const axios = require("axios"); | 
				
			|||
 | 
				
			|||
class Pushy extends NotificationProvider { | 
				
			|||
 | 
				
			|||
    name = "pushy"; | 
				
			|||
 | 
				
			|||
    async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { | 
				
			|||
        let okMsg = "Sent Successfully."; | 
				
			|||
 | 
				
			|||
        try { | 
				
			|||
            await axios.post(`https://api.pushy.me/push?api_key=${notification.pushyAPIKey}`, { | 
				
			|||
                "to": notification.pushyToken, | 
				
			|||
                "data": { | 
				
			|||
                    "message": "Uptime-Kuma" | 
				
			|||
                }, | 
				
			|||
                "notification": { | 
				
			|||
                    "body": msg, | 
				
			|||
                    "badge": 1, | 
				
			|||
                    "sound": "ping.aiff" | 
				
			|||
                } | 
				
			|||
            }) | 
				
			|||
            return okMsg; | 
				
			|||
        } catch (error) { | 
				
			|||
            this.throwGeneralAxiosError(error) | 
				
			|||
        } | 
				
			|||
    } | 
				
			|||
} | 
				
			|||
 | 
				
			|||
module.exports = Pushy; | 
				
			|||
@ -0,0 +1,66 @@ | 
				
			|||
const NotificationProvider = require("./notification-provider"); | 
				
			|||
const axios = require("axios"); | 
				
			|||
const Slack = require("./slack"); | 
				
			|||
const { setting } = require("../util-server"); | 
				
			|||
const { getMonitorRelativeURL, UP, DOWN } = require("../../src/util"); | 
				
			|||
 | 
				
			|||
class RocketChat extends NotificationProvider { | 
				
			|||
 | 
				
			|||
    name = "rocket.chat"; | 
				
			|||
 | 
				
			|||
    async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { | 
				
			|||
        let okMsg = "Sent Successfully."; | 
				
			|||
        try { | 
				
			|||
            if (heartbeatJSON == null) { | 
				
			|||
                let data = { | 
				
			|||
                    "text": msg, | 
				
			|||
                    "channel": notification.rocketchannel, | 
				
			|||
                    "username": notification.rocketusername, | 
				
			|||
                    "icon_emoji": notification.rocketiconemo, | 
				
			|||
                }; | 
				
			|||
                await axios.post(notification.rocketwebhookURL, data); | 
				
			|||
                return okMsg; | 
				
			|||
            } | 
				
			|||
 | 
				
			|||
            const time = heartbeatJSON["time"]; | 
				
			|||
 | 
				
			|||
            let data = { | 
				
			|||
                "text": "Uptime Kuma Alert", | 
				
			|||
                "channel": notification.rocketchannel, | 
				
			|||
                "username": notification.rocketusername, | 
				
			|||
                "icon_emoji": notification.rocketiconemo, | 
				
			|||
                "attachments": [ | 
				
			|||
                    { | 
				
			|||
                        "title": "Uptime Kuma Alert *Time (UTC)*\n" + time, | 
				
			|||
                        "text": "*Message*\n" + msg, | 
				
			|||
                    } | 
				
			|||
                ] | 
				
			|||
            }; | 
				
			|||
 | 
				
			|||
            // Color
 | 
				
			|||
            if (heartbeatJSON.status === DOWN) { | 
				
			|||
                data.attachments[0].color = "#ff0000"; | 
				
			|||
            } else { | 
				
			|||
                data.attachments[0].color = "#32cd32"; | 
				
			|||
            } | 
				
			|||
 | 
				
			|||
            if (notification.rocketbutton) { | 
				
			|||
                await Slack.deprecateURL(notification.rocketbutton); | 
				
			|||
            } | 
				
			|||
 | 
				
			|||
            const baseURL = await setting("primaryBaseURL"); | 
				
			|||
 | 
				
			|||
            if (baseURL) { | 
				
			|||
                data.attachments[0].title_link = baseURL + getMonitorRelativeURL(monitorJSON.id); | 
				
			|||
            } | 
				
			|||
 | 
				
			|||
            await axios.post(notification.rocketwebhookURL, data); | 
				
			|||
            return okMsg; | 
				
			|||
        } catch (error) { | 
				
			|||
            this.throwGeneralAxiosError(error); | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
    } | 
				
			|||
} | 
				
			|||
 | 
				
			|||
module.exports = RocketChat; | 
				
			|||
@ -0,0 +1,27 @@ | 
				
			|||
const NotificationProvider = require("./notification-provider"); | 
				
			|||
const axios = require("axios"); | 
				
			|||
 | 
				
			|||
class Signal extends NotificationProvider { | 
				
			|||
 | 
				
			|||
    name = "signal"; | 
				
			|||
 | 
				
			|||
    async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { | 
				
			|||
        let okMsg = "Sent Successfully."; | 
				
			|||
 | 
				
			|||
        try { | 
				
			|||
            let data = { | 
				
			|||
                "message": msg, | 
				
			|||
                "number": notification.signalNumber, | 
				
			|||
                "recipients": notification.signalRecipients.replace(/\s/g, "").split(","), | 
				
			|||
            }; | 
				
			|||
            let config = {}; | 
				
			|||
 | 
				
			|||
            await axios.post(notification.signalURL, data, config) | 
				
			|||
            return okMsg; | 
				
			|||
        } catch (error) { | 
				
			|||
            this.throwGeneralAxiosError(error) | 
				
			|||
        } | 
				
			|||
    } | 
				
			|||
} | 
				
			|||
 | 
				
			|||
module.exports = Signal; | 
				
			|||
@ -0,0 +1,98 @@ | 
				
			|||
const NotificationProvider = require("./notification-provider"); | 
				
			|||
const axios = require("axios"); | 
				
			|||
const { setSettings, setting } = require("../util-server"); | 
				
			|||
const { getMonitorRelativeURL } = require("../../src/util"); | 
				
			|||
 | 
				
			|||
class Slack extends NotificationProvider { | 
				
			|||
 | 
				
			|||
    name = "slack"; | 
				
			|||
 | 
				
			|||
    /** | 
				
			|||
     * Deprecated property notification.slackbutton | 
				
			|||
     * Set it as primary base url if this is not yet set. | 
				
			|||
     */ | 
				
			|||
    static async deprecateURL(url) { | 
				
			|||
        let currentPrimaryBaseURL = await setting("primaryBaseURL"); | 
				
			|||
 | 
				
			|||
        if (!currentPrimaryBaseURL) { | 
				
			|||
            console.log("Move the url to be the primary base URL"); | 
				
			|||
            await setSettings("general", { | 
				
			|||
                primaryBaseURL: url, | 
				
			|||
            }); | 
				
			|||
        } else { | 
				
			|||
            console.log("Already there, no need to move the primary base URL"); | 
				
			|||
        } | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { | 
				
			|||
        let okMsg = "Sent Successfully."; | 
				
			|||
        try { | 
				
			|||
            if (heartbeatJSON == null) { | 
				
			|||
                let data = { | 
				
			|||
                    "text": msg, | 
				
			|||
                    "channel": notification.slackchannel, | 
				
			|||
                    "username": notification.slackusername, | 
				
			|||
                    "icon_emoji": notification.slackiconemo, | 
				
			|||
                }; | 
				
			|||
                await axios.post(notification.slackwebhookURL, data); | 
				
			|||
                return okMsg; | 
				
			|||
            } | 
				
			|||
 | 
				
			|||
            const time = heartbeatJSON["time"]; | 
				
			|||
            let data = { | 
				
			|||
                "text": "Uptime Kuma Alert", | 
				
			|||
                "channel": notification.slackchannel, | 
				
			|||
                "username": notification.slackusername, | 
				
			|||
                "icon_emoji": notification.slackiconemo, | 
				
			|||
                "blocks": [{ | 
				
			|||
                    "type": "header", | 
				
			|||
                    "text": { | 
				
			|||
                        "type": "plain_text", | 
				
			|||
                        "text": "Uptime Kuma Alert", | 
				
			|||
                    }, | 
				
			|||
                }, | 
				
			|||
                { | 
				
			|||
                    "type": "section", | 
				
			|||
                    "fields": [{ | 
				
			|||
                        "type": "mrkdwn", | 
				
			|||
                        "text": "*Message*\n" + msg, | 
				
			|||
                    }, | 
				
			|||
                    { | 
				
			|||
                        "type": "mrkdwn", | 
				
			|||
                        "text": "*Time (UTC)*\n" + time, | 
				
			|||
                    }], | 
				
			|||
                }], | 
				
			|||
            }; | 
				
			|||
 | 
				
			|||
            if (notification.slackbutton) { | 
				
			|||
                await Slack.deprecateURL(notification.slackbutton); | 
				
			|||
            } | 
				
			|||
 | 
				
			|||
            const baseURL = await setting("primaryBaseURL"); | 
				
			|||
 | 
				
			|||
            // Button
 | 
				
			|||
            if (baseURL) { | 
				
			|||
                data.blocks.push({ | 
				
			|||
                    "type": "actions", | 
				
			|||
                    "elements": [{ | 
				
			|||
                        "type": "button", | 
				
			|||
                        "text": { | 
				
			|||
                            "type": "plain_text", | 
				
			|||
                            "text": "Visit Uptime Kuma", | 
				
			|||
                        }, | 
				
			|||
                        "value": "Uptime-Kuma", | 
				
			|||
                        "url": baseURL + getMonitorRelativeURL(monitorJSON.id), | 
				
			|||
                    }], | 
				
			|||
                }); | 
				
			|||
            } | 
				
			|||
 | 
				
			|||
            await axios.post(notification.slackwebhookURL, data); | 
				
			|||
            return okMsg; | 
				
			|||
        } catch (error) { | 
				
			|||
            this.throwGeneralAxiosError(error); | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
    } | 
				
			|||
} | 
				
			|||
 | 
				
			|||
module.exports = Slack; | 
				
			|||
@ -0,0 +1,48 @@ | 
				
			|||
const nodemailer = require("nodemailer"); | 
				
			|||
const NotificationProvider = require("./notification-provider"); | 
				
			|||
 | 
				
			|||
class SMTP extends NotificationProvider { | 
				
			|||
 | 
				
			|||
    name = "smtp"; | 
				
			|||
 | 
				
			|||
    async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { | 
				
			|||
 | 
				
			|||
        const config = { | 
				
			|||
            host: notification.smtpHost, | 
				
			|||
            port: notification.smtpPort, | 
				
			|||
            secure: notification.smtpSecure, | 
				
			|||
        }; | 
				
			|||
 | 
				
			|||
        // Should fix the issue in https://github.com/louislam/uptime-kuma/issues/26#issuecomment-896373904
 | 
				
			|||
        if (notification.smtpUsername || notification.smtpPassword) { | 
				
			|||
            config.auth = { | 
				
			|||
                user: notification.smtpUsername, | 
				
			|||
                pass: notification.smtpPassword, | 
				
			|||
            }; | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        let transporter = nodemailer.createTransport(config); | 
				
			|||
 | 
				
			|||
        let bodyTextContent = msg; | 
				
			|||
        if (heartbeatJSON) { | 
				
			|||
            bodyTextContent = `${msg}\nTime (UTC): ${heartbeatJSON["time"]}`; | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        // send mail with defined transport object
 | 
				
			|||
        await transporter.sendMail({ | 
				
			|||
            from: notification.smtpFrom, | 
				
			|||
            cc: notification.smtpCC, | 
				
			|||
            bcc: notification.smtpBCC, | 
				
			|||
            to: notification.smtpTo, | 
				
			|||
            subject: msg, | 
				
			|||
            text: bodyTextContent, | 
				
			|||
            tls: { | 
				
			|||
                rejectUnauthorized: notification.smtpIgnoreTLSError || false, | 
				
			|||
            }, | 
				
			|||
        }); | 
				
			|||
 | 
				
			|||
        return "Sent Successfully."; | 
				
			|||
    } | 
				
			|||
} | 
				
			|||
 | 
				
			|||
module.exports = SMTP; | 
				
			|||
@ -0,0 +1,124 @@ | 
				
			|||
const NotificationProvider = require("./notification-provider"); | 
				
			|||
const axios = require("axios"); | 
				
			|||
const { DOWN, UP } = require("../../src/util"); | 
				
			|||
 | 
				
			|||
class Teams extends NotificationProvider { | 
				
			|||
    name = "teams"; | 
				
			|||
 | 
				
			|||
    _statusMessageFactory = (status, monitorName) => { | 
				
			|||
        if (status === DOWN) { | 
				
			|||
            return `🔴 Application [${monitorName}] went down`; | 
				
			|||
        } else if (status === UP) { | 
				
			|||
            return `✅ Application [${monitorName}] is back online`; | 
				
			|||
        } | 
				
			|||
        return "Notification"; | 
				
			|||
    }; | 
				
			|||
 | 
				
			|||
    _getThemeColor = (status) => { | 
				
			|||
        if (status === DOWN) { | 
				
			|||
            return "ff0000"; | 
				
			|||
        } | 
				
			|||
        if (status === UP) { | 
				
			|||
            return "00e804"; | 
				
			|||
        } | 
				
			|||
        return "008cff"; | 
				
			|||
    }; | 
				
			|||
 | 
				
			|||
    _notificationPayloadFactory = ({ | 
				
			|||
        status, | 
				
			|||
        monitorMessage, | 
				
			|||
        monitorName, | 
				
			|||
        monitorUrl, | 
				
			|||
    }) => { | 
				
			|||
        const notificationMessage = this._statusMessageFactory( | 
				
			|||
            status, | 
				
			|||
            monitorName | 
				
			|||
        ); | 
				
			|||
 | 
				
			|||
        const facts = []; | 
				
			|||
 | 
				
			|||
        if (monitorName) { | 
				
			|||
            facts.push({ | 
				
			|||
                name: "Monitor", | 
				
			|||
                value: monitorName, | 
				
			|||
            }); | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        if (monitorUrl) { | 
				
			|||
            facts.push({ | 
				
			|||
                name: "URL", | 
				
			|||
                value: monitorUrl, | 
				
			|||
            }); | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        return { | 
				
			|||
            "@context": "https://schema.org/extensions", | 
				
			|||
            "@type": "MessageCard", | 
				
			|||
            themeColor: this._getThemeColor(status), | 
				
			|||
            summary: notificationMessage, | 
				
			|||
            sections: [ | 
				
			|||
                { | 
				
			|||
                    activityImage: | 
				
			|||
                        "https://raw.githubusercontent.com/louislam/uptime-kuma/master/public/icon.png", | 
				
			|||
                    activityTitle: "**Uptime Kuma**", | 
				
			|||
                }, | 
				
			|||
                { | 
				
			|||
                    activityTitle: notificationMessage, | 
				
			|||
                }, | 
				
			|||
                { | 
				
			|||
                    activityTitle: "**Description**", | 
				
			|||
                    text: monitorMessage, | 
				
			|||
                    facts, | 
				
			|||
                }, | 
				
			|||
            ], | 
				
			|||
        }; | 
				
			|||
    }; | 
				
			|||
 | 
				
			|||
    _sendNotification = async (webhookUrl, payload) => { | 
				
			|||
        await axios.post(webhookUrl, payload); | 
				
			|||
    }; | 
				
			|||
 | 
				
			|||
    _handleGeneralNotification = (webhookUrl, msg) => { | 
				
			|||
        const payload = this._notificationPayloadFactory({ | 
				
			|||
            monitorMessage: msg | 
				
			|||
        }); | 
				
			|||
 | 
				
			|||
        return this._sendNotification(webhookUrl, payload); | 
				
			|||
    }; | 
				
			|||
 | 
				
			|||
    async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { | 
				
			|||
        let okMsg = "Sent Successfully."; | 
				
			|||
 | 
				
			|||
        try { | 
				
			|||
            if (heartbeatJSON == null) { | 
				
			|||
                await this._handleGeneralNotification(notification.webhookUrl, msg); | 
				
			|||
                return okMsg; | 
				
			|||
            } | 
				
			|||
 | 
				
			|||
            let url; | 
				
			|||
 | 
				
			|||
            if (monitorJSON["type"] === "port") { | 
				
			|||
                url = monitorJSON["hostname"]; | 
				
			|||
                if (monitorJSON["port"]) { | 
				
			|||
                    url += ":" + monitorJSON["port"]; | 
				
			|||
                } | 
				
			|||
            } else { | 
				
			|||
                url = monitorJSON["url"]; | 
				
			|||
            } | 
				
			|||
 | 
				
			|||
            const payload = this._notificationPayloadFactory({ | 
				
			|||
                monitorMessage: heartbeatJSON.msg, | 
				
			|||
                monitorName: monitorJSON.name, | 
				
			|||
                monitorUrl: url, | 
				
			|||
                status: heartbeatJSON.status, | 
				
			|||
            }); | 
				
			|||
 | 
				
			|||
            await this._sendNotification(notification.webhookUrl, payload); | 
				
			|||
            return okMsg; | 
				
			|||
        } catch (error) { | 
				
			|||
            this.throwGeneralAxiosError(error); | 
				
			|||
        } | 
				
			|||
    } | 
				
			|||
} | 
				
			|||
 | 
				
			|||
module.exports = Teams; | 
				
			|||
@ -0,0 +1,27 @@ | 
				
			|||
const NotificationProvider = require("./notification-provider"); | 
				
			|||
const axios = require("axios"); | 
				
			|||
 | 
				
			|||
class Telegram extends NotificationProvider { | 
				
			|||
 | 
				
			|||
    name = "telegram"; | 
				
			|||
 | 
				
			|||
    async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { | 
				
			|||
        let okMsg = "Sent Successfully."; | 
				
			|||
 | 
				
			|||
        try { | 
				
			|||
            await axios.get(`https://api.telegram.org/bot${notification.telegramBotToken}/sendMessage`, { | 
				
			|||
                params: { | 
				
			|||
                    chat_id: notification.telegramChatID, | 
				
			|||
                    text: msg, | 
				
			|||
                }, | 
				
			|||
            }) | 
				
			|||
            return okMsg; | 
				
			|||
 | 
				
			|||
        } catch (error) { | 
				
			|||
            let msg = (error.response.data.description) ? error.response.data.description : "Error without description" | 
				
			|||
            throw new Error(msg) | 
				
			|||
        } | 
				
			|||
    } | 
				
			|||
} | 
				
			|||
 | 
				
			|||
module.exports = Telegram; | 
				
			|||
@ -0,0 +1,44 @@ | 
				
			|||
const NotificationProvider = require("./notification-provider"); | 
				
			|||
const axios = require("axios"); | 
				
			|||
const FormData = require("form-data"); | 
				
			|||
 | 
				
			|||
class Webhook extends NotificationProvider { | 
				
			|||
 | 
				
			|||
    name = "webhook"; | 
				
			|||
 | 
				
			|||
    async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { | 
				
			|||
        let okMsg = "Sent Successfully."; | 
				
			|||
 | 
				
			|||
        try { | 
				
			|||
            let data = { | 
				
			|||
                heartbeat: heartbeatJSON, | 
				
			|||
                monitor: monitorJSON, | 
				
			|||
                msg, | 
				
			|||
            }; | 
				
			|||
            let finalData; | 
				
			|||
            let config = {}; | 
				
			|||
 | 
				
			|||
            if (notification.webhookContentType === "form-data") { | 
				
			|||
                finalData = new FormData(); | 
				
			|||
                finalData.append("data", JSON.stringify(data)); | 
				
			|||
 | 
				
			|||
                config = { | 
				
			|||
                    headers: finalData.getHeaders(), | 
				
			|||
                } | 
				
			|||
 | 
				
			|||
            } else { | 
				
			|||
                finalData = data; | 
				
			|||
            } | 
				
			|||
 | 
				
			|||
            await axios.post(notification.webhookURL, finalData, config) | 
				
			|||
            return okMsg; | 
				
			|||
 | 
				
			|||
        } catch (error) { | 
				
			|||
            this.throwGeneralAxiosError(error) | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
} | 
				
			|||
 | 
				
			|||
module.exports = Webhook; | 
				
			|||
@ -0,0 +1,191 @@ | 
				
			|||
let express = require("express"); | 
				
			|||
const { allowDevAllOrigin, getSettings, setting } = require("../util-server"); | 
				
			|||
const { R } = require("redbean-node"); | 
				
			|||
const server = require("../server"); | 
				
			|||
const apicache = require("../modules/apicache"); | 
				
			|||
const Monitor = require("../model/monitor"); | 
				
			|||
const dayjs = require("dayjs"); | 
				
			|||
const { UP } = require("../../src/util"); | 
				
			|||
let router = express.Router(); | 
				
			|||
 | 
				
			|||
let cache = apicache.middleware; | 
				
			|||
let io = server.io; | 
				
			|||
 | 
				
			|||
router.get("/api/entry-page", async (_, response) => { | 
				
			|||
    allowDevAllOrigin(response); | 
				
			|||
    response.json(server.entryPage); | 
				
			|||
}); | 
				
			|||
 | 
				
			|||
router.get("/api/push/:pushToken", async (request, response) => { | 
				
			|||
    try { | 
				
			|||
        let pushToken = request.params.pushToken; | 
				
			|||
        let msg = request.query.msg || "OK"; | 
				
			|||
        let ping = request.query.ping; | 
				
			|||
 | 
				
			|||
        let monitor = await R.findOne("monitor", " push_token = ? AND active = 1 ", [ | 
				
			|||
            pushToken | 
				
			|||
        ]); | 
				
			|||
 | 
				
			|||
        if (! monitor) { | 
				
			|||
            throw new Error("Monitor not found or not active."); | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        let bean = R.dispense("heartbeat"); | 
				
			|||
        bean.monitor_id = monitor.id; | 
				
			|||
        bean.time = R.isoDateTime(dayjs.utc()); | 
				
			|||
        bean.status = UP; | 
				
			|||
        bean.msg = msg; | 
				
			|||
        bean.ping = ping; | 
				
			|||
 | 
				
			|||
        await R.store(bean); | 
				
			|||
 | 
				
			|||
        io.to(monitor.user_id).emit("heartbeat", bean.toJSON()); | 
				
			|||
        Monitor.sendStats(io, monitor.id, monitor.user_id); | 
				
			|||
 | 
				
			|||
        response.json({ | 
				
			|||
            ok: true, | 
				
			|||
        }); | 
				
			|||
    } catch (e) { | 
				
			|||
        response.json({ | 
				
			|||
            ok: false, | 
				
			|||
            msg: e.message | 
				
			|||
        }); | 
				
			|||
    } | 
				
			|||
}); | 
				
			|||
 | 
				
			|||
// Status Page Config
 | 
				
			|||
router.get("/api/status-page/config", async (_request, response) => { | 
				
			|||
    allowDevAllOrigin(response); | 
				
			|||
 | 
				
			|||
    let config = await getSettings("statusPage"); | 
				
			|||
 | 
				
			|||
    if (! config.statusPageTheme) { | 
				
			|||
        config.statusPageTheme = "light"; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    if (! config.statusPagePublished) { | 
				
			|||
        config.statusPagePublished = true; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    if (! config.title) { | 
				
			|||
        config.title = "Uptime Kuma"; | 
				
			|||
    } | 
				
			|||
 | 
				
			|||
    response.json(config); | 
				
			|||
}); | 
				
			|||
 | 
				
			|||
// Status Page - Get the current Incident
 | 
				
			|||
// Can fetch only if published
 | 
				
			|||
router.get("/api/status-page/incident", async (_, response) => { | 
				
			|||
    allowDevAllOrigin(response); | 
				
			|||
 | 
				
			|||
    try { | 
				
			|||
        await checkPublished(); | 
				
			|||
 | 
				
			|||
        let incident = await R.findOne("incident", " pin = 1 AND active = 1"); | 
				
			|||
 | 
				
			|||
        if (incident) { | 
				
			|||
            incident = incident.toPublicJSON(); | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        response.json({ | 
				
			|||
            ok: true, | 
				
			|||
            incident, | 
				
			|||
        }); | 
				
			|||
 | 
				
			|||
    } catch (error) { | 
				
			|||
        send403(response, error.message); | 
				
			|||
    } | 
				
			|||
}); | 
				
			|||
 | 
				
			|||
// Status Page - Monitor List
 | 
				
			|||
// Can fetch only if published
 | 
				
			|||
router.get("/api/status-page/monitor-list", cache("5 minutes"), async (_request, response) => { | 
				
			|||
    allowDevAllOrigin(response); | 
				
			|||
 | 
				
			|||
    try { | 
				
			|||
        await checkPublished(); | 
				
			|||
        const publicGroupList = []; | 
				
			|||
        let list = await R.find("group", " public = 1 ORDER BY weight "); | 
				
			|||
 | 
				
			|||
        for (let groupBean of list) { | 
				
			|||
            publicGroupList.push(await groupBean.toPublicJSON()); | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        response.json(publicGroupList); | 
				
			|||
 | 
				
			|||
    } catch (error) { | 
				
			|||
        send403(response, error.message); | 
				
			|||
    } | 
				
			|||
}); | 
				
			|||
 | 
				
			|||
// Status Page Polling Data
 | 
				
			|||
// Can fetch only if published
 | 
				
			|||
router.get("/api/status-page/heartbeat", cache("5 minutes"), async (_request, response) => { | 
				
			|||
    allowDevAllOrigin(response); | 
				
			|||
 | 
				
			|||
    try { | 
				
			|||
        await checkPublished(); | 
				
			|||
 | 
				
			|||
        let heartbeatList = {}; | 
				
			|||
        let uptimeList = {}; | 
				
			|||
 | 
				
			|||
        let monitorIDList = await R.getCol(` | 
				
			|||
            SELECT monitor_group.monitor_id FROM monitor_group, \`group\` | 
				
			|||
            WHERE monitor_group.group_id = \`group\`.id
 | 
				
			|||
            AND public = 1 | 
				
			|||
        `);
 | 
				
			|||
 | 
				
			|||
        for (let monitorID of monitorIDList) { | 
				
			|||
            let list = await R.getAll(` | 
				
			|||
                    SELECT * FROM heartbeat | 
				
			|||
                    WHERE monitor_id = ? | 
				
			|||
                    ORDER BY time DESC | 
				
			|||
                    LIMIT 50 | 
				
			|||
            `, [
 | 
				
			|||
                monitorID, | 
				
			|||
            ]); | 
				
			|||
 | 
				
			|||
            list = R.convertToBeans("heartbeat", list); | 
				
			|||
            heartbeatList[monitorID] = list.reverse().map(row => row.toPublicJSON()); | 
				
			|||
 | 
				
			|||
            const type = 24; | 
				
			|||
            uptimeList[`${monitorID}_${type}`] = await Monitor.calcUptime(type, monitorID); | 
				
			|||
        } | 
				
			|||
 | 
				
			|||
        response.json({ | 
				
			|||
            heartbeatList, | 
				
			|||
            uptimeList | 
				
			|||
        }); | 
				
			|||
 | 
				
			|||
    } catch (error) { | 
				
			|||
        send403(response, error.message); | 
				
			|||
    } | 
				
			|||
}); | 
				
			|||
 | 
				
			|||
async function checkPublished() { | 
				
			|||
    if (! await isPublished()) { | 
				
			|||
        throw new Error("The status page is not published"); | 
				
			|||
    } | 
				
			|||
} | 
				
			|||
 | 
				
			|||
/** | 
				
			|||
 * Default is published | 
				
			|||
 * @returns {Promise<boolean>} | 
				
			|||
 */ | 
				
			|||
async function isPublished() { | 
				
			|||
    const value = await setting("statusPagePublished"); | 
				
			|||
    if (value === null) { | 
				
			|||
        return true; | 
				
			|||
    } | 
				
			|||
    return value; | 
				
			|||
} | 
				
			|||
 | 
				
			|||
function send403(res, msg = "") { | 
				
			|||
    res.status(403).json({ | 
				
			|||
        "status": "fail", | 
				
			|||
        "msg": msg, | 
				
			|||
    }); | 
				
			|||
} | 
				
			|||
 | 
				
			|||
module.exports = router; | 
				
			|||
Some files were not shown because too many files changed in this diff
					Loading…
					
					
				
		Reference in new issue