Merge branch 'master' into XMPP-retry-test
This commit is contained in:
commit
b4c2624c69
18
.github/workflows/auto-test.yml
vendored
18
.github/workflows/auto-test.yml
vendored
@ -22,6 +22,7 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
os: [macos-latest, ubuntu-22.04, windows-latest, ubuntu-22.04-arm]
|
os: [macos-latest, ubuntu-22.04, windows-latest, ubuntu-22.04-arm]
|
||||||
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
||||||
@ -41,13 +42,13 @@ jobs:
|
|||||||
id: node-modules-cache
|
id: node-modules-cache
|
||||||
with:
|
with:
|
||||||
path: node_modules
|
path: node_modules
|
||||||
key: node-modules-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
|
key: node-modules-${{ runner.os }}-node${{ matrix.node }}-${{ hashFiles('**/package-lock.json') }}
|
||||||
|
|
||||||
- name: Use Node.js ${{ matrix.node }}
|
- name: Use Node.js ${{ matrix.node }}
|
||||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node }}
|
node-version: ${{ matrix.node }}
|
||||||
- run: npm install
|
- run: npm clean-install --no-fund
|
||||||
|
|
||||||
- name: Rebuild native modules for ARM64
|
- name: Rebuild native modules for ARM64
|
||||||
if: matrix.os == 'ubuntu-22.04-arm'
|
if: matrix.os == 'ubuntu-22.04-arm'
|
||||||
@ -65,6 +66,7 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
strategy:
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
node: [ 20, 22 ]
|
node: [ 20, 22 ]
|
||||||
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
||||||
@ -86,8 +88,8 @@ jobs:
|
|||||||
docker run --rm --platform linux/arm/v7 \
|
docker run --rm --platform linux/arm/v7 \
|
||||||
-v $PWD:/workspace \
|
-v $PWD:/workspace \
|
||||||
-w /workspace \
|
-w /workspace \
|
||||||
arm32v7/node:${{ matrix.node }}-slim \
|
arm32v7/node:${{ matrix.node }} \
|
||||||
bash -c "npm install --production"
|
npm clean-install --no-fund --production
|
||||||
|
|
||||||
check-linters:
|
check-linters:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@ -104,13 +106,13 @@ jobs:
|
|||||||
id: node-modules-cache
|
id: node-modules-cache
|
||||||
with:
|
with:
|
||||||
path: node_modules
|
path: node_modules
|
||||||
key: node-modules-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
|
key: node-modules-${{ runner.os }}-node${{ matrix.node }}-${{ hashFiles('**/package-lock.json') }}
|
||||||
|
|
||||||
- name: Use Node.js 20
|
- name: Use Node.js 20
|
||||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
- run: npm install
|
- run: npm clean-install --no-fund
|
||||||
- run: npm run lint:prod
|
- run: npm run lint:prod
|
||||||
|
|
||||||
e2e-test:
|
e2e-test:
|
||||||
@ -129,13 +131,13 @@ jobs:
|
|||||||
id: node-modules-cache
|
id: node-modules-cache
|
||||||
with:
|
with:
|
||||||
path: node_modules
|
path: node_modules
|
||||||
key: node-modules-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
|
key: node-modules-${{ runner.os }}-node${{ matrix.node }}-${{ hashFiles('**/package-lock.json') }}
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||||
with:
|
with:
|
||||||
node-version: 22
|
node-version: 22
|
||||||
- run: npm install
|
- run: npm clean-install --no-fund
|
||||||
|
|
||||||
- name: Rebuild native modules for ARM64
|
- name: Rebuild native modules for ARM64
|
||||||
run: npm rebuild @louislam/sqlite3
|
run: npm rebuild @louislam/sqlite3
|
||||||
|
|||||||
@ -2,10 +2,22 @@ const { MonitorType } = require("./monitor-type");
|
|||||||
const { log, UP } = require("../../src/util");
|
const { log, UP } = require("../../src/util");
|
||||||
const mqtt = require("mqtt");
|
const mqtt = require("mqtt");
|
||||||
const jsonata = require("jsonata");
|
const jsonata = require("jsonata");
|
||||||
|
const { ConditionVariable } = require("../monitor-conditions/variables");
|
||||||
|
const { defaultStringOperators, defaultNumberOperators } = require("../monitor-conditions/operators");
|
||||||
|
const { ConditionExpressionGroup } = require("../monitor-conditions/expression");
|
||||||
|
const { evaluateExpressionGroup } = require("../monitor-conditions/evaluator");
|
||||||
|
|
||||||
class MqttMonitorType extends MonitorType {
|
class MqttMonitorType extends MonitorType {
|
||||||
name = "mqtt";
|
name = "mqtt";
|
||||||
|
|
||||||
|
supportsConditions = true;
|
||||||
|
|
||||||
|
conditionVariables = [
|
||||||
|
new ConditionVariable("topic", defaultStringOperators),
|
||||||
|
new ConditionVariable("message", defaultStringOperators),
|
||||||
|
new ConditionVariable("json_value", defaultStringOperators.concat(defaultNumberOperators)),
|
||||||
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
@ -19,32 +31,98 @@ class MqttMonitorType extends MonitorType {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (monitor.mqttCheckType == null || monitor.mqttCheckType === "") {
|
if (monitor.mqttCheckType == null || monitor.mqttCheckType === "") {
|
||||||
// use old default
|
|
||||||
monitor.mqttCheckType = "keyword";
|
monitor.mqttCheckType = "keyword";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (monitor.mqttCheckType === "keyword") {
|
// Check if conditions are defined
|
||||||
if (receivedMessage != null && receivedMessage.includes(monitor.mqttSuccessMessage)) {
|
const conditions = monitor.conditions ? ConditionExpressionGroup.fromMonitor(monitor) : null;
|
||||||
heartbeat.msg = `Topic: ${messageTopic}; Message: ${receivedMessage}`;
|
const hasConditions = conditions && conditions.children && conditions.children.length > 0;
|
||||||
heartbeat.status = UP;
|
|
||||||
} else {
|
if (hasConditions) {
|
||||||
throw Error(`Message Mismatch - Topic: ${monitor.mqttTopic}; Message: ${receivedMessage}`);
|
await this.checkConditions(monitor, heartbeat, messageTopic, receivedMessage, conditions);
|
||||||
}
|
} else if (monitor.mqttCheckType === "keyword") {
|
||||||
|
this.checkKeyword(monitor, heartbeat, messageTopic, receivedMessage);
|
||||||
} else if (monitor.mqttCheckType === "json-query") {
|
} else if (monitor.mqttCheckType === "json-query") {
|
||||||
const parsedMessage = JSON.parse(receivedMessage);
|
await this.checkJsonQuery(monitor, heartbeat, receivedMessage);
|
||||||
|
|
||||||
let expression = jsonata(monitor.jsonPath);
|
|
||||||
|
|
||||||
let result = await expression.evaluate(parsedMessage);
|
|
||||||
|
|
||||||
if (result?.toString() === monitor.expectedValue) {
|
|
||||||
heartbeat.msg = "Message received, expected value is found";
|
|
||||||
heartbeat.status = UP;
|
|
||||||
} else {
|
|
||||||
throw new Error("Message received but value is not equal to expected value, value was: [" + result + "]");
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
throw Error("Unknown MQTT Check Type");
|
throw new Error("Unknown MQTT Check Type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check using keyword matching
|
||||||
|
* @param {object} monitor Monitor object
|
||||||
|
* @param {object} heartbeat Heartbeat object
|
||||||
|
* @param {string} messageTopic Received MQTT topic
|
||||||
|
* @param {string} receivedMessage Received MQTT message
|
||||||
|
* @returns {void}
|
||||||
|
* @throws {Error} If keyword is not found in message
|
||||||
|
*/
|
||||||
|
checkKeyword(monitor, heartbeat, messageTopic, receivedMessage) {
|
||||||
|
if (receivedMessage != null && receivedMessage.includes(monitor.mqttSuccessMessage)) {
|
||||||
|
heartbeat.msg = `Topic: ${messageTopic}; Message: ${receivedMessage}`;
|
||||||
|
heartbeat.status = UP;
|
||||||
|
} else {
|
||||||
|
throw new Error(`Message Mismatch - Topic: ${monitor.mqttTopic}; Message: ${receivedMessage}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check using JSONata query
|
||||||
|
* @param {object} monitor Monitor object
|
||||||
|
* @param {object} heartbeat Heartbeat object
|
||||||
|
* @param {string} receivedMessage Received MQTT message
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async checkJsonQuery(monitor, heartbeat, receivedMessage) {
|
||||||
|
const parsedMessage = JSON.parse(receivedMessage);
|
||||||
|
const expression = jsonata(monitor.jsonPath);
|
||||||
|
const result = await expression.evaluate(parsedMessage);
|
||||||
|
|
||||||
|
if (result?.toString() === monitor.expectedValue) {
|
||||||
|
heartbeat.msg = "Message received, expected value is found";
|
||||||
|
heartbeat.status = UP;
|
||||||
|
} else {
|
||||||
|
throw new Error("Message received but value is not equal to expected value, value was: [" + result + "]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check using conditions system
|
||||||
|
* @param {object} monitor Monitor object
|
||||||
|
* @param {object} heartbeat Heartbeat object
|
||||||
|
* @param {string} messageTopic Received MQTT topic
|
||||||
|
* @param {string} receivedMessage Received MQTT message
|
||||||
|
* @param {ConditionExpressionGroup} conditions Parsed conditions
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async checkConditions(monitor, heartbeat, messageTopic, receivedMessage, conditions) {
|
||||||
|
let jsonValue = null;
|
||||||
|
|
||||||
|
// Parse JSON and extract value if jsonPath is defined
|
||||||
|
if (monitor.jsonPath) {
|
||||||
|
try {
|
||||||
|
const parsedMessage = JSON.parse(receivedMessage);
|
||||||
|
const expression = jsonata(monitor.jsonPath);
|
||||||
|
jsonValue = await expression.evaluate(parsedMessage);
|
||||||
|
} catch (e) {
|
||||||
|
// JSON parsing failed, jsonValue remains null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const conditionData = {
|
||||||
|
topic: messageTopic,
|
||||||
|
message: receivedMessage,
|
||||||
|
json_value: jsonValue?.toString() ?? "",
|
||||||
|
};
|
||||||
|
|
||||||
|
const conditionsResult = evaluateExpressionGroup(conditions, conditionData);
|
||||||
|
|
||||||
|
if (conditionsResult) {
|
||||||
|
heartbeat.msg = `Topic: ${messageTopic}; Message: ${receivedMessage}`;
|
||||||
|
heartbeat.status = UP;
|
||||||
|
} else {
|
||||||
|
throw new Error(`Conditions not met - Topic: ${messageTopic}; Message: ${receivedMessage}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -7,7 +7,7 @@
|
|||||||
<h5 class="modal-title">
|
<h5 class="modal-title">
|
||||||
{{ $t("Add API Key") }}
|
{{ $t("Add API Key") }}
|
||||||
</h5>
|
</h5>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" />
|
<button type="button" class="btn-close" data-bs-dismiss="modal" :aria-label="$t('Close')" />
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<!-- Name -->
|
<!-- Name -->
|
||||||
@ -67,7 +67,7 @@
|
|||||||
<h5 class="modal-title">
|
<h5 class="modal-title">
|
||||||
{{ $t("Key Added") }}
|
{{ $t("Key Added") }}
|
||||||
</h5>
|
</h5>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" />
|
<button type="button" class="btn-close" data-bs-dismiss="modal" :aria-label="$t('Close')" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
|
|||||||
@ -4,11 +4,16 @@
|
|||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h5 class="modal-title">
|
<h5 class="modal-title">
|
||||||
{{ $t("Badge Generator", [monitor.name]) }}
|
{{ $t("Badge Link Generator", [monitor.name]) }}
|
||||||
</h5>
|
</h5>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" />
|
<button type="button" class="btn-close" data-bs-dismiss="modal" :aria-label="$t('Close')" />
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
|
<i18n-t keypath="Badge Link Generator Helptext" tag="p" class="form-text mb-3">
|
||||||
|
<template #documentation>
|
||||||
|
<a href="https://github.com/louislam/uptime-kuma/wiki/Badge" target="_blank" rel="noopener noreferrer">{{ $t("documentation") }}</a>
|
||||||
|
</template>
|
||||||
|
</i18n-t>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="type" class="form-label">{{ $t("Badge Type") }}</label>
|
<label for="type" class="form-label">{{ $t("Badge Type") }}</label>
|
||||||
<select id="type" v-model="badge.type" class="form-select">
|
<select id="type" v-model="badge.type" class="form-select">
|
||||||
@ -6,17 +6,17 @@
|
|||||||
<h5 id="exampleModalLabel" class="modal-title">
|
<h5 id="exampleModalLabel" class="modal-title">
|
||||||
{{ title || $t("Confirm") }}
|
{{ title || $t("Confirm") }}
|
||||||
</h5>
|
</h5>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" />
|
<button type="button" class="btn-close" data-bs-dismiss="modal" :aria-label="$t('Close')" />
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn" :class="btnStyle" data-bs-dismiss="modal" @click="yes">
|
<button type="button" class="btn" :class="btnStyle" data-bs-dismiss="modal" @click="yes">
|
||||||
{{ yesText }}
|
{{ yesText || $t("Yes") }}
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" @click="no">
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" @click="no">
|
||||||
{{ noText }}
|
{{ noText || $t("No") }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -37,12 +37,12 @@ export default {
|
|||||||
/** Text to use as yes */
|
/** Text to use as yes */
|
||||||
yesText: {
|
yesText: {
|
||||||
type: String,
|
type: String,
|
||||||
default: "Yes", // TODO: No idea what to translate this
|
default: null,
|
||||||
},
|
},
|
||||||
/** Text to use as no */
|
/** Text to use as no */
|
||||||
noText: {
|
noText: {
|
||||||
type: String,
|
type: String,
|
||||||
default: "No",
|
default: null,
|
||||||
},
|
},
|
||||||
/** Title to show on modal. Defaults to translated version of "Config" */
|
/** Title to show on modal. Defaults to translated version of "Config" */
|
||||||
title: {
|
title: {
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
<h5 class="modal-title">
|
<h5 class="modal-title">
|
||||||
{{ $t("New Group") }}
|
{{ $t("New Group") }}
|
||||||
</h5>
|
</h5>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" />
|
<button type="button" class="btn-close" data-bs-dismiss="modal" :aria-label="$t('Close')" />
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<form @submit.prevent="confirm">
|
<form @submit.prevent="confirm">
|
||||||
|
|||||||
@ -7,7 +7,7 @@
|
|||||||
<h5 id="exampleModalLabel" class="modal-title">
|
<h5 id="exampleModalLabel" class="modal-title">
|
||||||
{{ $t("Setup Docker Host") }}
|
{{ $t("Setup Docker Host") }}
|
||||||
</h5>
|
</h5>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" />
|
<button type="button" class="btn-close" data-bs-dismiss="modal" :aria-label="$t('Close')" />
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
<h5 class="modal-title">
|
<h5 class="modal-title">
|
||||||
{{ $t("Monitor Setting", [monitor.name]) }}
|
{{ $t("Monitor Setting", [monitor.name]) }}
|
||||||
</h5>
|
</h5>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" />
|
<button type="button" class="btn-close" data-bs-dismiss="modal" :aria-label="$t('Close')" />
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="my-3 form-check">
|
<div class="my-3 form-check">
|
||||||
@ -31,10 +31,10 @@
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
class="btn btn-primary btn-add-group me-2"
|
class="btn btn-primary btn-add-group me-2"
|
||||||
@click="$refs.badgeGeneratorDialog.show(monitor.id, monitor.name)"
|
@click="$refs.badgeLinkGeneratorDialog.show(monitor.id, monitor.name)"
|
||||||
>
|
>
|
||||||
<font-awesome-icon icon="certificate" />
|
<font-awesome-icon icon="certificate" />
|
||||||
{{ $t("Open Badge Generator") }}
|
{{ $t("Open Badge Link Generator") }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -46,16 +46,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<BadgeGeneratorDialog ref="badgeGeneratorDialog" />
|
<BadgeLinkGeneratorDialog ref="badgeLinkGeneratorDialog" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Modal } from "bootstrap";
|
import { Modal } from "bootstrap";
|
||||||
import BadgeGeneratorDialog from "./BadgeGeneratorDialog.vue";
|
import BadgeLinkGeneratorDialog from "./BadgeLinkGeneratorDialog.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
BadgeGeneratorDialog
|
BadgeLinkGeneratorDialog
|
||||||
},
|
},
|
||||||
props: {},
|
props: {},
|
||||||
emits: [],
|
emits: [],
|
||||||
|
|||||||
@ -7,7 +7,7 @@
|
|||||||
<h5 id="exampleModalLabel" class="modal-title">
|
<h5 id="exampleModalLabel" class="modal-title">
|
||||||
{{ $t("Setup Notification") }}
|
{{ $t("Setup Notification") }}
|
||||||
</h5>
|
</h5>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" />
|
<button type="button" class="btn-close" data-bs-dismiss="modal" :aria-label="$t('Close')" />
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
|
|||||||
@ -7,7 +7,7 @@
|
|||||||
<h5 id="exampleModalLabel" class="modal-title">
|
<h5 id="exampleModalLabel" class="modal-title">
|
||||||
{{ $t("Setup Proxy") }}
|
{{ $t("Setup Proxy") }}
|
||||||
</h5>
|
</h5>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" />
|
<button type="button" class="btn-close" data-bs-dismiss="modal" :aria-label="$t('Close')" />
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
|
|||||||
@ -7,7 +7,7 @@
|
|||||||
<h5 id="exampleModalLabel" class="modal-title">
|
<h5 id="exampleModalLabel" class="modal-title">
|
||||||
{{ $t("Add a Remote Browser") }}
|
{{ $t("Add a Remote Browser") }}
|
||||||
</h5>
|
</h5>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" />
|
<button type="button" class="btn-close" data-bs-dismiss="modal" :aria-label="$t('Close')" />
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
|
|||||||
@ -6,10 +6,10 @@
|
|||||||
<h5 class="modal-title">
|
<h5 class="modal-title">
|
||||||
{{ $t("Browser Screenshot") }}
|
{{ $t("Browser Screenshot") }}
|
||||||
</h5>
|
</h5>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" />
|
<button type="button" class="btn-close" data-bs-dismiss="modal" :aria-label="$t('Close')" />
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body"></div>
|
<div class="modal-body"></div>
|
||||||
<img :src="imageURL" alt="screenshot of the website">
|
<img :src="imageURL" :alt="$t('screenshot of the website')">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -7,7 +7,7 @@
|
|||||||
<h5 id="exampleModalLabel" class="modal-title">
|
<h5 id="exampleModalLabel" class="modal-title">
|
||||||
{{ $t("Edit Tag") }}
|
{{ $t("Edit Tag") }}
|
||||||
</h5>
|
</h5>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" />
|
<button type="button" class="btn-close" data-bs-dismiss="modal" :aria-label="$t('Close')" />
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
|
|||||||
@ -9,7 +9,7 @@
|
|||||||
<span v-if="twoFAStatus == true" class="badge bg-primary">{{ $t("Active") }}</span>
|
<span v-if="twoFAStatus == true" class="badge bg-primary">{{ $t("Active") }}</span>
|
||||||
<span v-if="twoFAStatus == false" class="badge bg-primary">{{ $t("Inactive") }}</span>
|
<span v-if="twoFAStatus == false" class="badge bg-primary">{{ $t("Inactive") }}</span>
|
||||||
</h5>
|
</h5>
|
||||||
<button :disabled="processing" type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" />
|
<button :disabled="processing" type="button" class="btn-close" data-bs-dismiss="modal" :aria-label="$t('Close')" />
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
|
|||||||
@ -85,12 +85,12 @@ export default {
|
|||||||
|
|
||||||
title() {
|
title() {
|
||||||
if (this.type === "1y") {
|
if (this.type === "1y") {
|
||||||
return `1${this.$t("-year")}`;
|
return `1 ${this.$tc("year", 1)}`;
|
||||||
}
|
}
|
||||||
if (this.type === "720") {
|
if (this.type === "720") {
|
||||||
return `30${this.$t("-day")}`;
|
return `30 ${this.$tc("day", 30)}`;
|
||||||
}
|
}
|
||||||
return `24${this.$t("-hour")}`;
|
return `24 ${this.$tc("hour", 24)}`;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -20,7 +20,7 @@
|
|||||||
<div
|
<div
|
||||||
class="btn-group"
|
class="btn-group"
|
||||||
role="group"
|
role="group"
|
||||||
aria-label="Basic checkbox toggle button group"
|
:aria-label="$t('Basic checkbox toggle button group')"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
id="btncheck1"
|
id="btncheck1"
|
||||||
@ -69,7 +69,7 @@
|
|||||||
<div
|
<div
|
||||||
class="btn-group"
|
class="btn-group"
|
||||||
role="group"
|
role="group"
|
||||||
aria-label="Basic checkbox toggle button group"
|
:aria-label="$t('Basic checkbox toggle button group')"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
id="btncheck4"
|
id="btncheck4"
|
||||||
|
|||||||
@ -52,10 +52,8 @@
|
|||||||
"now": "now",
|
"now": "now",
|
||||||
"time ago": "{0} ago",
|
"time ago": "{0} ago",
|
||||||
"day": "day | days",
|
"day": "day | days",
|
||||||
"-day": "-day",
|
"hour": "hour | hours",
|
||||||
"hour": "hour",
|
"year": "year | years",
|
||||||
"-hour": "-hour",
|
|
||||||
"-year": "-year",
|
|
||||||
"Response": "Response",
|
"Response": "Response",
|
||||||
"Ping": "Ping",
|
"Ping": "Ping",
|
||||||
"Monitor Type": "Monitor Type",
|
"Monitor Type": "Monitor Type",
|
||||||
@ -911,8 +909,9 @@
|
|||||||
"Monitor Setting": "{0}'s Monitor Setting",
|
"Monitor Setting": "{0}'s Monitor Setting",
|
||||||
"Show Clickable Link": "Show Clickable Link",
|
"Show Clickable Link": "Show Clickable Link",
|
||||||
"Show Clickable Link Description": "If checked everyone who have access to this status page can have access to monitor URL.",
|
"Show Clickable Link Description": "If checked everyone who have access to this status page can have access to monitor URL.",
|
||||||
"Open Badge Generator": "Open Badge Generator",
|
"Open Badge Link Generator": "Open Badge Link Generator",
|
||||||
"Badge Generator": "{0}'s Badge Generator",
|
"Badge Link Generator": "{0}'s Badge Link Generator",
|
||||||
|
"Badge Link Generator Helptext": "Badge links are available for all monitors assigned to public status pages. For more information, please see the {documentation}.",
|
||||||
"Badge Type": "Badge Type",
|
"Badge Type": "Badge Type",
|
||||||
"Badge Duration (in hours)": "Badge Duration (in hours)",
|
"Badge Duration (in hours)": "Badge Duration (in hours)",
|
||||||
"Badge Label": "Badge Label",
|
"Badge Label": "Badge Label",
|
||||||
@ -1121,6 +1120,8 @@
|
|||||||
"less than or equal to": "less than or equal to",
|
"less than or equal to": "less than or equal to",
|
||||||
"greater than or equal to": "greater than or equal to",
|
"greater than or equal to": "greater than or equal to",
|
||||||
"record": "record",
|
"record": "record",
|
||||||
|
"message": "message",
|
||||||
|
"json_value": "JSON value",
|
||||||
"Notification Channel": "Notification Channel",
|
"Notification Channel": "Notification Channel",
|
||||||
"Sound": "Sound",
|
"Sound": "Sound",
|
||||||
"Alphanumerical string and hyphens only": "Alphanumerical string and hyphens only",
|
"Alphanumerical string and hyphens only": "Alphanumerical string and hyphens only",
|
||||||
@ -1246,6 +1247,9 @@
|
|||||||
"minimumIntervalWarning": "Intervals below 20 seconds may result in poor performance.",
|
"minimumIntervalWarning": "Intervals below 20 seconds may result in poor performance.",
|
||||||
"lowIntervalWarning": "Are you sure want to set the interval value below 20 seconds? Performance may be degraded, particularly if there are a large number of monitors.",
|
"lowIntervalWarning": "Are you sure want to set the interval value below 20 seconds? Performance may be degraded, particularly if there are a large number of monitors.",
|
||||||
"imageResetConfirmation": "Image reset to default",
|
"imageResetConfirmation": "Image reset to default",
|
||||||
|
"screenshot of the website": "Screenshot of the website",
|
||||||
|
"Basic checkbox toggle button group": "Basic checkbox toggle button group",
|
||||||
|
"Basic radio toggle button group": "Basic radio toggle button group",
|
||||||
"mtls-auth-server-cert-label": "Cert",
|
"mtls-auth-server-cert-label": "Cert",
|
||||||
"mtls-auth-server-cert-placeholder": "Cert body",
|
"mtls-auth-server-cert-placeholder": "Cert body",
|
||||||
"mtls-auth-server-key-label": "Key",
|
"mtls-auth-server-key-label": "Key",
|
||||||
|
|||||||
@ -237,7 +237,7 @@
|
|||||||
>
|
>
|
||||||
<h4 class="col-4 col-sm-12">{{ pingTitle(true) }}</h4>
|
<h4 class="col-4 col-sm-12">{{ pingTitle(true) }}</h4>
|
||||||
<p class="col-4 col-sm-12 mb-0 mb-sm-2">
|
<p class="col-4 col-sm-12 mb-0 mb-sm-2">
|
||||||
(24{{ $t("-hour") }})
|
({{ 24 }} {{ $tc("hour", 24) }})
|
||||||
</p>
|
</p>
|
||||||
<span class="col-4 col-sm-12 num">
|
<span class="col-4 col-sm-12 num">
|
||||||
<CountUp :value="avgPing" />
|
<CountUp :value="avgPing" />
|
||||||
@ -250,7 +250,7 @@
|
|||||||
>
|
>
|
||||||
<h4 class="col-4 col-sm-12">{{ $t("Uptime") }}</h4>
|
<h4 class="col-4 col-sm-12">{{ $t("Uptime") }}</h4>
|
||||||
<p class="col-4 col-sm-12 mb-0 mb-sm-2">
|
<p class="col-4 col-sm-12 mb-0 mb-sm-2">
|
||||||
(24{{ $t("-hour") }})
|
({{ 24 }} {{ $tc("hour", 24) }})
|
||||||
</p>
|
</p>
|
||||||
<span class="col-4 col-sm-12 num">
|
<span class="col-4 col-sm-12 num">
|
||||||
<Uptime :monitor="monitor" type="24" />
|
<Uptime :monitor="monitor" type="24" />
|
||||||
@ -263,7 +263,7 @@
|
|||||||
>
|
>
|
||||||
<h4 class="col-4 col-sm-12">{{ $t("Uptime") }}</h4>
|
<h4 class="col-4 col-sm-12">{{ $t("Uptime") }}</h4>
|
||||||
<p class="col-4 col-sm-12 mb-0 mb-sm-2">
|
<p class="col-4 col-sm-12 mb-0 mb-sm-2">
|
||||||
(30{{ $t("-day") }})
|
({{ 30 }} {{ $tc("day", 30) }})
|
||||||
</p>
|
</p>
|
||||||
<span class="col-4 col-sm-12 num">
|
<span class="col-4 col-sm-12 num">
|
||||||
<Uptime :monitor="monitor" type="720" />
|
<Uptime :monitor="monitor" type="720" />
|
||||||
@ -276,7 +276,7 @@
|
|||||||
>
|
>
|
||||||
<h4 class="col-4 col-sm-12">{{ $t("Uptime") }}</h4>
|
<h4 class="col-4 col-sm-12">{{ $t("Uptime") }}</h4>
|
||||||
<p class="col-4 col-sm-12 mb-0 mb-sm-2">
|
<p class="col-4 col-sm-12 mb-0 mb-sm-2">
|
||||||
(1{{ $t("-year") }})
|
({{ 1 }} {{ $tc("year", 1) }})
|
||||||
</p>
|
</p>
|
||||||
<span class="col-4 col-sm-12 num">
|
<span class="col-4 col-sm-12 num">
|
||||||
<Uptime :monitor="monitor" type="1y" />
|
<Uptime :monitor="monitor" type="1y" />
|
||||||
|
|||||||
@ -33,7 +33,7 @@
|
|||||||
{{ $t("setupDatabaseChooseDatabase") }}
|
{{ $t("setupDatabaseChooseDatabase") }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="btn-group" role="group" aria-label="Basic radio toggle button group">
|
<div class="btn-group" role="group" :aria-label="$t('Basic radio toggle button group')">
|
||||||
<template v-if="info.isEnabledEmbeddedMariaDB">
|
<template v-if="info.isEnabledEmbeddedMariaDB">
|
||||||
<input id="btnradio3" v-model="dbConfig.type" type="radio" class="btn-check" autocomplete="off" value="embedded-mariadb">
|
<input id="btnradio3" v-model="dbConfig.type" type="radio" class="btn-check" autocomplete="off" value="embedded-mariadb">
|
||||||
|
|
||||||
|
|||||||
@ -12,9 +12,10 @@ const { UP, PENDING } = require("../../../src/util");
|
|||||||
* @param {string} receivedMessage what message is received from the mqtt channel
|
* @param {string} receivedMessage what message is received from the mqtt channel
|
||||||
* @param {string} monitorTopic which MQTT topic is monitored (wildcards are allowed)
|
* @param {string} monitorTopic which MQTT topic is monitored (wildcards are allowed)
|
||||||
* @param {string} publishTopic to which MQTT topic the message is sent
|
* @param {string} publishTopic to which MQTT topic the message is sent
|
||||||
|
* @param {string|null} conditions JSON string of conditions or null
|
||||||
* @returns {Promise<Heartbeat>} the heartbeat produced by the check
|
* @returns {Promise<Heartbeat>} the heartbeat produced by the check
|
||||||
*/
|
*/
|
||||||
async function testMqtt(mqttSuccessMessage, mqttCheckType, receivedMessage, monitorTopic = "test", publishTopic = "test") {
|
async function testMqtt(mqttSuccessMessage, mqttCheckType, receivedMessage, monitorTopic = "test", publishTopic = "test", conditions = null) {
|
||||||
const hiveMQContainer = await new HiveMQContainer().start();
|
const hiveMQContainer = await new HiveMQContainer().start();
|
||||||
const connectionString = hiveMQContainer.getConnectionString();
|
const connectionString = hiveMQContainer.getConnectionString();
|
||||||
const mqttMonitorType = new MqttMonitorType();
|
const mqttMonitorType = new MqttMonitorType();
|
||||||
@ -30,6 +31,7 @@ async function testMqtt(mqttSuccessMessage, mqttCheckType, receivedMessage, moni
|
|||||||
mqttSuccessMessage: mqttSuccessMessage, // for keywords
|
mqttSuccessMessage: mqttSuccessMessage, // for keywords
|
||||||
expectedValue: mqttSuccessMessage, // for json-query
|
expectedValue: mqttSuccessMessage, // for json-query
|
||||||
mqttCheckType: mqttCheckType,
|
mqttCheckType: mqttCheckType,
|
||||||
|
conditions: conditions, // for conditions system
|
||||||
};
|
};
|
||||||
const heartbeat = {
|
const heartbeat = {
|
||||||
msg: "",
|
msg: "",
|
||||||
@ -157,4 +159,67 @@ describe("MqttMonitorType", {
|
|||||||
new Error("Message received but value is not equal to expected value, value was: [present]")
|
new Error("Message received but value is not equal to expected value, value was: [present]")
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Conditions system tests
|
||||||
|
test("check() sets status to UP when message condition matches (contains)", async () => {
|
||||||
|
const conditions = JSON.stringify([
|
||||||
|
{
|
||||||
|
type: "expression",
|
||||||
|
variable: "message",
|
||||||
|
operator: "contains",
|
||||||
|
value: "KEYWORD"
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
const heartbeat = await testMqtt("", null, "-> KEYWORD <-", "test", "test", conditions);
|
||||||
|
assert.strictEqual(heartbeat.status, UP);
|
||||||
|
assert.strictEqual(heartbeat.msg, "Topic: test; Message: -> KEYWORD <-");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("check() sets status to UP when topic condition matches (equals)", async () => {
|
||||||
|
const conditions = JSON.stringify([
|
||||||
|
{
|
||||||
|
type: "expression",
|
||||||
|
variable: "topic",
|
||||||
|
operator: "equals",
|
||||||
|
value: "sensors/temp"
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
const heartbeat = await testMqtt("", null, "any message", "sensors/temp", "sensors/temp", conditions);
|
||||||
|
assert.strictEqual(heartbeat.status, UP);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("check() rejects when message condition does not match", async () => {
|
||||||
|
const conditions = JSON.stringify([
|
||||||
|
{
|
||||||
|
type: "expression",
|
||||||
|
variable: "message",
|
||||||
|
operator: "contains",
|
||||||
|
value: "EXPECTED"
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
await assert.rejects(
|
||||||
|
testMqtt("", null, "actual message without keyword", "test", "test", conditions),
|
||||||
|
new Error("Conditions not met - Topic: test; Message: actual message without keyword")
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("check() sets status to UP with multiple conditions (AND)", async () => {
|
||||||
|
const conditions = JSON.stringify([
|
||||||
|
{
|
||||||
|
type: "expression",
|
||||||
|
variable: "topic",
|
||||||
|
operator: "equals",
|
||||||
|
value: "test"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "expression",
|
||||||
|
variable: "message",
|
||||||
|
operator: "contains",
|
||||||
|
value: "success",
|
||||||
|
andOr: "and"
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
const heartbeat = await testMqtt("", null, "operation success", "test", "test", conditions);
|
||||||
|
assert.strictEqual(heartbeat.status, UP);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -178,6 +178,9 @@ test.describe("Status Page", () => {
|
|||||||
await page.getByTestId("analytics-id-input").fill(plausibleAnalyticsDomainsUrls);
|
await page.getByTestId("analytics-id-input").fill(plausibleAnalyticsDomainsUrls);
|
||||||
await page.getByTestId("save-button").click();
|
await page.getByTestId("save-button").click();
|
||||||
await screenshot(testInfo, page);
|
await screenshot(testInfo, page);
|
||||||
|
await page.waitForFunction((scriptUrl) => {
|
||||||
|
return document.head.innerHTML.includes(scriptUrl);
|
||||||
|
}, plausibleAnalyticsScriptUrl, { timeout: 5000 });
|
||||||
expect(await page.locator("head").innerHTML()).toContain(plausibleAnalyticsScriptUrl);
|
expect(await page.locator("head").innerHTML()).toContain(plausibleAnalyticsScriptUrl);
|
||||||
expect(await page.locator("head").innerHTML()).toContain(plausibleAnalyticsDomainsUrls);
|
expect(await page.locator("head").innerHTML()).toContain(plausibleAnalyticsDomainsUrls);
|
||||||
|
|
||||||
@ -188,6 +191,9 @@ test.describe("Status Page", () => {
|
|||||||
await page.getByTestId("analytics-id-input").fill(matomoSiteId);
|
await page.getByTestId("analytics-id-input").fill(matomoSiteId);
|
||||||
await page.getByTestId("save-button").click();
|
await page.getByTestId("save-button").click();
|
||||||
await screenshot(testInfo, page);
|
await screenshot(testInfo, page);
|
||||||
|
await page.waitForFunction((url) => {
|
||||||
|
return document.head.innerHTML.includes(url);
|
||||||
|
}, matomoUrl, { timeout: 5000 });
|
||||||
expect(await page.locator("head").innerHTML()).toContain(matomoUrl);
|
expect(await page.locator("head").innerHTML()).toContain(matomoUrl);
|
||||||
expect(await page.locator("head").innerHTML()).toContain(matomoSiteId);
|
expect(await page.locator("head").innerHTML()).toContain(matomoSiteId);
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user