Compare commits

...

8 commits

Author SHA1 Message Date
Louis Lam
6d43b44dce
Merge d81bf8aaf9 into 778363a948 2024-11-11 10:08:57 +00:00
Dorence Deng
778363a948
fix(notification): #5323 fix EditMonitor.vue useToast error (#5333)
Some checks failed
Auto Test / e2e-test (push) Has been cancelled
Auto Test / armv7-simple-test (18, ARMv7) (push) Has been cancelled
Auto Test / armv7-simple-test (20, ARMv7) (push) Has been cancelled
Auto Test / check-linters (push) Has been cancelled
CodeQL / Analyze (push) Has been cancelled
Merge Conflict Labeler / Labeling (push) Has been cancelled
validate / json-yaml-validate (push) Has been cancelled
validate / validate (push) Has been cancelled
Auto Test / auto-test (18, ARM64) (push) Has been cancelled
Auto Test / auto-test (18, macos-latest) (push) Has been cancelled
Auto Test / auto-test (18, ubuntu-latest) (push) Has been cancelled
Auto Test / auto-test (18, windows-latest) (push) Has been cancelled
Auto Test / auto-test (20, ARM64) (push) Has been cancelled
Auto Test / auto-test (20, macos-latest) (push) Has been cancelled
Auto Test / auto-test (20, ubuntu-latest) (push) Has been cancelled
Auto Test / auto-test (20, windows-latest) (push) Has been cancelled
2024-11-11 11:08:43 +01:00
Abhigyan Mohanta
6899603eb7
fix #5314 Email Address Friendly Name Error (#5318)
Some checks failed
CodeQL / Analyze (push) Has been cancelled
Merge Conflict Labeler / Labeling (push) Has been cancelled
validate / json-yaml-validate (push) Has been cancelled
validate / validate (push) Has been cancelled
Auto Test / armv7-simple-test (18, ARMv7) (push) Has been cancelled
Auto Test / armv7-simple-test (20, ARMv7) (push) Has been cancelled
Auto Test / check-linters (push) Has been cancelled
Auto Test / e2e-test (push) Has been cancelled
Auto Test / auto-test (18, ARM64) (push) Has been cancelled
Auto Test / auto-test (18, macos-latest) (push) Has been cancelled
Auto Test / auto-test (18, ubuntu-latest) (push) Has been cancelled
Auto Test / auto-test (18, windows-latest) (push) Has been cancelled
Auto Test / auto-test (20, ARM64) (push) Has been cancelled
Auto Test / auto-test (20, macos-latest) (push) Has been cancelled
Auto Test / auto-test (20, ubuntu-latest) (push) Has been cancelled
Auto Test / auto-test (20, windows-latest) (push) Has been cancelled
2024-11-09 23:30:08 +08:00
Daan Meijer
13ea190298
only allow for valid urls in slack button (#5312)
Some checks failed
Auto Test / armv7-simple-test (18, ARMv7) (push) Has been cancelled
Auto Test / armv7-simple-test (20, ARMv7) (push) Has been cancelled
Auto Test / check-linters (push) Has been cancelled
Auto Test / e2e-test (push) Has been cancelled
CodeQL / Analyze (push) Has been cancelled
Merge Conflict Labeler / Labeling (push) Has been cancelled
validate / json-yaml-validate (push) Has been cancelled
validate / validate (push) Has been cancelled
Auto Test / auto-test (18, ARM64) (push) Has been cancelled
Auto Test / auto-test (18, macos-latest) (push) Has been cancelled
Auto Test / auto-test (18, ubuntu-latest) (push) Has been cancelled
Auto Test / auto-test (18, windows-latest) (push) Has been cancelled
Auto Test / auto-test (20, ARM64) (push) Has been cancelled
Auto Test / auto-test (20, macos-latest) (push) Has been cancelled
Auto Test / auto-test (20, ubuntu-latest) (push) Has been cancelled
Auto Test / auto-test (20, windows-latest) (push) Has been cancelled
2024-11-05 20:50:47 +01:00
Frank Elsinga
a7407a1b65
fix not setting a default for json monitor operations (#5295)
Some checks are pending
Auto Test / armv7-simple-test (18, ARMv7) (push) Waiting to run
Auto Test / auto-test (18, ARM64) (push) Blocked by required conditions
Auto Test / auto-test (18, macos-latest) (push) Blocked by required conditions
Auto Test / auto-test (18, ubuntu-latest) (push) Blocked by required conditions
Auto Test / auto-test (18, windows-latest) (push) Blocked by required conditions
Auto Test / auto-test (20, ARM64) (push) Blocked by required conditions
Auto Test / auto-test (20, macos-latest) (push) Blocked by required conditions
Auto Test / auto-test (20, ubuntu-latest) (push) Blocked by required conditions
Auto Test / auto-test (20, windows-latest) (push) Blocked by required conditions
Auto Test / armv7-simple-test (20, ARMv7) (push) Waiting to run
Auto Test / check-linters (push) Waiting to run
Auto Test / e2e-test (push) Waiting to run
CodeQL / Analyze (push) Waiting to run
Merge Conflict Labeler / Labeling (push) Waiting to run
validate / json-yaml-validate (push) Waiting to run
validate / validate (push) Waiting to run
2024-11-05 20:35:42 +08:00
Louis Lam
5bcde56a0f
Publish docker images to ghcr.io (#5311) 2024-11-05 20:26:26 +08:00
Louis Lam
5864c6dd88
Fix: Embedded MariaDB startup issue (#5283) 2024-11-05 20:25:08 +08:00
Ionys
595b35fb15
fix(tags): Fix the tag deletion (#5298)
Some checks failed
Auto Test / armv7-simple-test (18, ARMv7) (push) Has been cancelled
Auto Test / armv7-simple-test (20, ARMv7) (push) Has been cancelled
Auto Test / check-linters (push) Has been cancelled
Auto Test / e2e-test (push) Has been cancelled
CodeQL / Analyze (push) Has been cancelled
Merge Conflict Labeler / Labeling (push) Has been cancelled
validate / json-yaml-validate (push) Has been cancelled
validate / validate (push) Has been cancelled
Auto Test / auto-test (18, ARM64) (push) Has been cancelled
Auto Test / auto-test (18, macos-latest) (push) Has been cancelled
Auto Test / auto-test (18, ubuntu-latest) (push) Has been cancelled
Auto Test / auto-test (18, windows-latest) (push) Has been cancelled
Auto Test / auto-test (20, ARM64) (push) Has been cancelled
Auto Test / auto-test (20, macos-latest) (push) Has been cancelled
Auto Test / auto-test (20, ubuntu-latest) (push) Has been cancelled
Auto Test / auto-test (20, windows-latest) (push) Has been cancelled
2024-11-02 13:09:23 +01:00
11 changed files with 129 additions and 64 deletions

View file

@ -0,0 +1,7 @@
exports.up = function (knex) {
return knex("monitor").whereNull("json_path_operator").update("json_path_operator", "==");
};
exports.down = function (knex) {
// changing the json_path_operator back to null for all "==" is not possible anymore
// we have lost the context which fields have been set explicitely in >= v2.0 and which would need to be reverted
};

View file

@ -6,14 +6,13 @@ import {
checkDocker, checkDocker,
checkTagExists, checkTagExists,
checkVersionFormat, checkVersionFormat,
dryRun, getRepoNames,
getRepoName,
pressAnyKey, pressAnyKey,
execSync, uploadArtifacts, execSync, uploadArtifacts,
} from "./lib.mjs"; } from "./lib.mjs";
import semver from "semver"; import semver from "semver";
const repoName = getRepoName(); const repoNames = getRepoNames();
const version = process.env.RELEASE_BETA_VERSION; const version = process.env.RELEASE_BETA_VERSION;
const githubToken = process.env.RELEASE_GITHUB_TOKEN; const githubToken = process.env.RELEASE_GITHUB_TOKEN;
@ -39,7 +38,7 @@ if (semverIdentifier[0] !== "beta") {
checkDocker(); checkDocker();
// Check if the tag exists // Check if the tag exists
await checkTagExists(repoName, version); await checkTagExists(repoNames, version);
// node extra/beta/update-version.js // node extra/beta/update-version.js
execSync("node ./extra/beta/update-version.js"); execSync("node ./extra/beta/update-version.js");
@ -48,16 +47,16 @@ execSync("node ./extra/beta/update-version.js");
buildDist(); buildDist();
// Build slim image (rootless) // Build slim image (rootless)
buildImage(repoName, [ "beta-slim-rootless", ver(version, "slim-rootless") ], "rootless", "BASE_IMAGE=louislam/uptime-kuma:base2-slim"); buildImage(repoNames, [ "beta-slim-rootless", ver(version, "slim-rootless") ], "rootless", "BASE_IMAGE=louislam/uptime-kuma:base2-slim");
// Build full image (rootless) // Build full image (rootless)
buildImage(repoName, [ "beta-rootless", ver(version, "rootless") ], "rootless"); buildImage(repoNames, [ "beta-rootless", ver(version, "rootless") ], "rootless");
// Build slim image // Build slim image
buildImage(repoName, [ "beta-slim", ver(version, "slim") ], "release", "BASE_IMAGE=louislam/uptime-kuma:base2-slim"); buildImage(repoNames, [ "beta-slim", ver(version, "slim") ], "release", "BASE_IMAGE=louislam/uptime-kuma:base2-slim");
// Build full image // Build full image
buildImage(repoName, [ "beta", version ], "release"); buildImage(repoNames, [ "beta", version ], "release");
await pressAnyKey(); await pressAnyKey();

View file

@ -6,11 +6,11 @@ import {
checkDocker, checkDocker,
checkTagExists, checkTagExists,
checkVersionFormat, checkVersionFormat,
getRepoName, getRepoNames,
pressAnyKey, execSync, uploadArtifacts pressAnyKey, execSync, uploadArtifacts
} from "./lib.mjs"; } from "./lib.mjs";
const repoName = getRepoName(); const repoNames = getRepoNames();
const version = process.env.RELEASE_VERSION; const version = process.env.RELEASE_VERSION;
const githubToken = process.env.RELEASE_GITHUB_TOKEN; const githubToken = process.env.RELEASE_GITHUB_TOKEN;
@ -28,7 +28,7 @@ checkVersionFormat(version);
checkDocker(); checkDocker();
// Check if the tag exists // Check if the tag exists
await checkTagExists(repoName, version); await checkTagExists(repoNames, version);
// node extra/beta/update-version.js // node extra/beta/update-version.js
execSync("node extra/update-version.js"); execSync("node extra/update-version.js");
@ -37,16 +37,16 @@ execSync("node extra/update-version.js");
buildDist(); buildDist();
// Build slim image (rootless) // Build slim image (rootless)
buildImage(repoName, [ "2-slim-rootless", ver(version, "slim-rootless") ], "rootless", "BASE_IMAGE=louislam/uptime-kuma:base2-slim"); buildImage(repoNames, [ "2-slim-rootless", ver(version, "slim-rootless") ], "rootless", "BASE_IMAGE=louislam/uptime-kuma:base2-slim");
// Build full image (rootless) // Build full image (rootless)
buildImage(repoName, [ "2-rootless", ver(version, "rootless") ], "rootless"); buildImage(repoNames, [ "2-rootless", ver(version, "rootless") ], "rootless");
// Build slim image // Build slim image
buildImage(repoName, [ "next-slim", "2-slim", ver(version, "slim") ], "release", "BASE_IMAGE=louislam/uptime-kuma:base2-slim"); buildImage(repoNames, [ "next-slim", "2-slim", ver(version, "slim") ], "release", "BASE_IMAGE=louislam/uptime-kuma:base2-slim");
// Build full image // Build full image
buildImage(repoName, [ "next", "2", version ], "release"); buildImage(repoNames, [ "next", "2", version ], "release");
await pressAnyKey(); await pressAnyKey();

View file

@ -24,8 +24,15 @@ export function checkDocker() {
/** /**
* Get Docker Hub repository name * Get Docker Hub repository name
*/ */
export function getRepoName() { export function getRepoNames() {
return process.env.RELEASE_REPO_NAME || "louislam/uptime-kuma"; if (process.env.RELEASE_REPO_NAMES) {
// Split by comma
return process.env.RELEASE_REPO_NAMES.split(",").map((name) => name.trim());
}
return [
"louislam/uptime-kuma",
"ghcr.io/louislam/uptime-kuma",
];
} }
/** /**
@ -42,7 +49,7 @@ export function buildDist() {
/** /**
* Build docker image and push to Docker Hub * Build docker image and push to Docker Hub
* @param {string} repoName Docker Hub repository name * @param {string[]} repoNames Docker Hub repository names
* @param {string[]} tags Docker image tags * @param {string[]} tags Docker image tags
* @param {string} target Dockerfile's target name * @param {string} target Dockerfile's target name
* @param {string} buildArgs Docker build args * @param {string} buildArgs Docker build args
@ -50,7 +57,7 @@ export function buildDist() {
* @param {string} platform Build platform * @param {string} platform Build platform
* @returns {void} * @returns {void}
*/ */
export function buildImage(repoName, tags, target, buildArgs = "", dockerfile = "docker/dockerfile", platform = "linux/amd64,linux/arm64,linux/arm/v7") { export function buildImage(repoNames, tags, target, buildArgs = "", dockerfile = "docker/dockerfile", platform = "linux/amd64,linux/arm64,linux/arm/v7") {
let args = [ let args = [
"buildx", "buildx",
"build", "build",
@ -60,9 +67,11 @@ export function buildImage(repoName, tags, target, buildArgs = "", dockerfile =
platform, platform,
]; ];
// Add tags for (let repoName of repoNames) {
for (let tag of tags) { // Add tags
args.push("-t", `${repoName}:${tag}`); for (let tag of tags) {
args.push("-t", `${repoName}:${tag}`);
}
} }
args = [ args = [

View file

@ -1,7 +1,7 @@
import { buildDist, buildImage, checkDocker, getRepoName } from "./lib.mjs"; import { buildDist, buildImage, checkDocker, getRepoNames } from "./lib.mjs";
// Docker Hub repository name // Docker Hub repository name
const repoName = getRepoName(); const repoNames = getRepoNames();
// Check if docker is running // Check if docker is running
checkDocker(); checkDocker();
@ -10,7 +10,7 @@ checkDocker();
buildDist(); buildDist();
// Build full image (rootless) // Build full image (rootless)
buildImage(repoName, [ "nightly2-rootless" ], "nightly-rootless"); buildImage(repoNames, [ "nightly2-rootless" ], "nightly-rootless");
// Build full image // Build full image
buildImage(repoName, [ "nightly2" ], "nightly"); buildImage(repoNames, [ "nightly2" ], "nightly");

View file

@ -296,7 +296,7 @@ class Database {
client: "mysql2", client: "mysql2",
connection: { connection: {
socketPath: embeddedMariaDB.socketPath, socketPath: embeddedMariaDB.socketPath,
user: "node", user: embeddedMariaDB.username,
database: "kuma", database: "kuma",
timezone: "Z", timezone: "Z",
typeCast: function (field, next) { typeCast: function (field, next) {

View file

@ -14,9 +14,15 @@ class EmbeddedMariaDB {
mariadbDataDir = "/app/data/mariadb"; mariadbDataDir = "/app/data/mariadb";
runDir = "/app/data/run/mariadb"; runDir = "/app/data/run";
socketPath = this.runDir + "/mysqld.sock"; socketPath = this.runDir + "/mariadb.sock";
/**
* The username to connect to the MariaDB
* @type {string}
*/
username = null;
/** /**
* @type {ChildProcessWithoutNullStreams} * @type {ChildProcessWithoutNullStreams}
@ -46,16 +52,42 @@ class EmbeddedMariaDB {
/** /**
* Start the embedded MariaDB * Start the embedded MariaDB
* @throws {Error} If the current user is not "node" or "root"
* @returns {Promise<void>|void} A promise that resolves when the MariaDB is started or void if it is already started * @returns {Promise<void>|void} A promise that resolves when the MariaDB is started or void if it is already started
*/ */
start() { start() {
// Check if the current user is "node" or "root"
this.username = require("os").userInfo().username;
if (this.username !== "node" && this.username !== "root") {
throw new Error("Embedded Mariadb supports only 'node' or 'root' user, but the current user is: " + this.username);
}
this.initDB();
this.startChildProcess();
return new Promise((resolve) => {
let interval = setInterval(() => {
if (this.started) {
clearInterval(interval);
resolve();
} else {
log.info("mariadb", "Waiting for Embedded MariaDB to start...");
}
}, 1000);
});
}
/**
* Start the child process
* @returns {void}
*/
startChildProcess() {
if (this.childProcess) { if (this.childProcess) {
log.info("mariadb", "Already started"); log.info("mariadb", "Already started");
return; return;
} }
this.initDB();
this.running = true; this.running = true;
log.info("mariadb", "Starting Embedded MariaDB"); log.info("mariadb", "Starting Embedded MariaDB");
this.childProcess = childProcess.spawn(this.exec, [ this.childProcess = childProcess.spawn(this.exec, [
@ -63,6 +95,8 @@ class EmbeddedMariaDB {
"--datadir=" + this.mariadbDataDir, "--datadir=" + this.mariadbDataDir,
`--socket=${this.socketPath}`, `--socket=${this.socketPath}`,
`--pid-file=${this.runDir}/mysqld.pid`, `--pid-file=${this.runDir}/mysqld.pid`,
// Don't add the following option, the mariadb will not report message to the console, which affects initDBAfterStarted()
// "--log-error=" + `${this.mariadbDataDir}/mariadb-error.log`,
]); ]);
this.childProcess.on("close", (code) => { this.childProcess.on("close", (code) => {
@ -72,8 +106,8 @@ class EmbeddedMariaDB {
log.info("mariadb", "Stopped Embedded MariaDB: " + code); log.info("mariadb", "Stopped Embedded MariaDB: " + code);
if (code !== 0) { if (code !== 0) {
log.info("mariadb", "Try to restart Embedded MariaDB as it is not stopped by user"); log.error("mariadb", "Try to restart Embedded MariaDB as it is not stopped by user");
this.start(); this.startChildProcess();
} }
}); });
@ -86,7 +120,7 @@ class EmbeddedMariaDB {
}); });
let handler = (data) => { let handler = (data) => {
log.debug("mariadb", data.toString("utf-8")); log.info("mariadb", data.toString("utf-8"));
if (data.toString("utf-8").includes("ready for connections")) { if (data.toString("utf-8").includes("ready for connections")) {
this.initDBAfterStarted(); this.initDBAfterStarted();
} }
@ -94,17 +128,6 @@ class EmbeddedMariaDB {
this.childProcess.stdout.on("data", handler); this.childProcess.stdout.on("data", handler);
this.childProcess.stderr.on("data", handler); this.childProcess.stderr.on("data", handler);
return new Promise((resolve) => {
let interval = setInterval(() => {
if (this.started) {
clearInterval(interval);
resolve();
} else {
log.info("mariadb", "Waiting for Embedded MariaDB to start...");
}
}, 1000);
});
} }
/** /**
@ -129,9 +152,11 @@ class EmbeddedMariaDB {
recursive: true, recursive: true,
}); });
let result = childProcess.spawnSync("mysql_install_db", [ let result = childProcess.spawnSync("mariadb-install-db", [
"--user=node", "--user=node",
"--ldata=" + this.mariadbDataDir, "--auth-root-socket-user=node",
"--datadir=" + this.mariadbDataDir,
"--auth-root-authentication-method=socket",
]); ]);
if (result.status !== 0) { if (result.status !== 0) {
@ -143,6 +168,17 @@ class EmbeddedMariaDB {
} }
} }
// Check the owner of the mariadb directory, and change it if necessary
let stat = fs.statSync(this.mariadbDataDir);
if (stat.uid !== 1000 || stat.gid !== 1000) {
fs.chownSync(this.mariadbDataDir, 1000, 1000);
}
// Check the permission of the mariadb directory, and change it if it is not 755
if (stat.mode !== 0o755) {
fs.chmodSync(this.mariadbDataDir, 0o755);
}
if (!fs.existsSync(this.runDir)) { if (!fs.existsSync(this.runDir)) {
log.info("mariadb", `Embedded MariaDB: ${this.runDir} is not found, create one now.`); log.info("mariadb", `Embedded MariaDB: ${this.runDir} is not found, create one now.`);
fs.mkdirSync(this.runDir, { fs.mkdirSync(this.runDir, {
@ -150,6 +186,13 @@ class EmbeddedMariaDB {
}); });
} }
stat = fs.statSync(this.runDir);
if (stat.uid !== 1000 || stat.gid !== 1000) {
fs.chownSync(this.runDir, 1000, 1000);
}
if (stat.mode !== 0o755) {
fs.chmodSync(this.runDir, 0o755);
}
} }
/** /**
@ -159,7 +202,7 @@ class EmbeddedMariaDB {
async initDBAfterStarted() { async initDBAfterStarted() {
const connection = mysql.createConnection({ const connection = mysql.createConnection({
socketPath: this.socketPath, socketPath: this.socketPath,
user: "node", user: this.username,
}); });
let result = await connection.execute("CREATE DATABASE IF NOT EXISTS `kuma`"); let result = await connection.execute("CREATE DATABASE IF NOT EXISTS `kuma`");

View file

@ -1522,7 +1522,7 @@ class Monitor extends BeanModel {
*/ */
static async getMonitorTag(monitorIDs) { static async getMonitorTag(monitorIDs) {
return await R.getAll(` return await R.getAll(`
SELECT monitor_tag.monitor_id, monitor_tag.tag_id, tag.name, tag.color SELECT monitor_tag.monitor_id, monitor_tag.tag_id, monitor_tag.value, tag.name, tag.color
FROM monitor_tag FROM monitor_tag
JOIN tag ON monitor_tag.tag_id = tag.id JOIN tag ON monitor_tag.tag_id = tag.id
WHERE monitor_tag.monitor_id IN (${monitorIDs.map((_) => "?").join(",")}) WHERE monitor_tag.monitor_id IN (${monitorIDs.map((_) => "?").join(",")})
@ -1567,6 +1567,8 @@ class Monitor extends BeanModel {
} }
tagsMap.get(row.monitor_id).push({ tagsMap.get(row.monitor_id).push({
tag_id: row.tag_id, tag_id: row.tag_id,
monitor_id: row.monitor_id,
value: row.value,
name: row.name, name: row.name,
color: row.color color: row.color
}); });

View file

@ -1,7 +1,7 @@
const NotificationProvider = require("./notification-provider"); const NotificationProvider = require("./notification-provider");
const axios = require("axios"); const axios = require("axios");
const { setSettings, setting } = require("../util-server"); const { setSettings, setting } = require("../util-server");
const { getMonitorRelativeURL, UP } = require("../../src/util"); const { getMonitorRelativeURL, UP, log } = require("../../src/util");
class Slack extends NotificationProvider { class Slack extends NotificationProvider {
name = "slack"; name = "slack";
@ -50,15 +50,20 @@ class Slack extends NotificationProvider {
const address = this.extractAddress(monitorJSON); const address = this.extractAddress(monitorJSON);
if (address) { if (address) {
actions.push({ try {
"type": "button", actions.push({
"text": { "type": "button",
"type": "plain_text", "text": {
"text": "Visit site", "type": "plain_text",
}, "text": "Visit site",
"value": "Site", },
"url": address, "value": "Site",
}); "url": new URL(address),
});
} catch (e) {
log.debug("slack", `Failed to parse address ${address} as URL`);
}
} }
return actions; return actions;

View file

@ -5,20 +5,20 @@
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label for="sendgrid-from-email" class="form-label">{{ $t("From Email") }}</label> <label for="sendgrid-from-email" class="form-label">{{ $t("From Email") }}</label>
<input id="sendgrid-from-email" v-model="$parent.notification.sendgridFromEmail" type="email" class="form-control" required> <input id="sendgrid-from-email" v-model="$parent.notification.sendgridFromEmail" type="text" class="form-control" required>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label for="sendgrid-to-email" class="form-label">{{ $t("To Email") }}</label> <label for="sendgrid-to-email" class="form-label">{{ $t("To Email") }}</label>
<input id="sendgrid-to-email" v-model="$parent.notification.sendgridToEmail" type="email" class="form-control" required> <input id="sendgrid-to-email" v-model="$parent.notification.sendgridToEmail" type="text" class="form-control" required>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label for="sendgrid-cc-email" class="form-label">{{ $t("smtpCC") }}</label> <label for="sendgrid-cc-email" class="form-label">{{ $t("smtpCC") }}</label>
<input id="sendgrid-cc-email" v-model="$parent.notification.sendgridCcEmail" type="email" class="form-control"> <input id="sendgrid-cc-email" v-model="$parent.notification.sendgridCcEmail" type="text" class="form-control">
<div class="form-text">{{ $t("Separate multiple email addresses with commas") }}</div> <div class="form-text">{{ $t("Separate multiple email addresses with commas") }}</div>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label for="sendgrid-bcc-email" class="form-label">{{ $t("smtpBCC") }}</label> <label for="sendgrid-bcc-email" class="form-label">{{ $t("smtpBCC") }}</label>
<input id="sendgrid-bcc-email" v-model="$parent.notification.sendgridBccEmail" type="email" class="form-control"> <input id="sendgrid-bcc-email" v-model="$parent.notification.sendgridBccEmail" type="text" class="form-control">
<small class="form-text text-muted">{{ $t("Separate multiple email addresses with commas") }}</small> <small class="form-text text-muted">{{ $t("Separate multiple email addresses with commas") }}</small>
</div> </div>
<div class="mb-3"> <div class="mb-3">

View file

@ -1065,7 +1065,7 @@ import { hostNameRegexPattern } from "../util-frontend";
import HiddenInput from "../components/HiddenInput.vue"; import HiddenInput from "../components/HiddenInput.vue";
import EditMonitorConditions from "../components/EditMonitorConditions.vue"; import EditMonitorConditions from "../components/EditMonitorConditions.vue";
const toast = useToast; const toast = useToast();
const pushTokenLength = 32; const pushTokenLength = 32;