Merge branch 'master' into feature/recovery-check-interval
This commit is contained in:
commit
03c358e85f
3
.github/workflows/beta-release.yml
vendored
3
.github/workflows/beta-release.yml
vendored
@ -50,7 +50,8 @@ jobs:
|
|||||||
git push origin --delete "release-${VERSION}" || true
|
git push origin --delete "release-${VERSION}" || true
|
||||||
# Delete local branch if it exists
|
# Delete local branch if it exists
|
||||||
git branch -D "release-${VERSION}" || true
|
git branch -D "release-${VERSION}" || true
|
||||||
# Create new branch from master
|
# For testing purpose
|
||||||
|
# git checkout beta-workflow
|
||||||
git checkout -b "release-${VERSION}"
|
git checkout -b "release-${VERSION}"
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
|
|||||||
@ -36,6 +36,9 @@ export default defineConfig({
|
|||||||
srcDir: "src",
|
srcDir: "src",
|
||||||
filename: "serviceWorker.ts",
|
filename: "serviceWorker.ts",
|
||||||
strategies: "injectManifest",
|
strategies: "injectManifest",
|
||||||
|
injectManifest: {
|
||||||
|
maximumFileSizeToCacheInBytes: 3 * 1024 * 1024, // 3 MiB
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
css: {
|
css: {
|
||||||
|
|||||||
@ -1,3 +1,6 @@
|
|||||||
|
import { createRequire } from "module";
|
||||||
|
const require = createRequire(import.meta.url);
|
||||||
|
|
||||||
const pkg = require("../../package.json");
|
const pkg = require("../../package.json");
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
const childProcess = require("child_process");
|
const childProcess = require("child_process");
|
||||||
@ -58,8 +61,13 @@ function commit(version) {
|
|||||||
throw new Error("commit error");
|
throw new Error("commit error");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: Push is handled by gh pr create in the release script
|
// Get the current branch name
|
||||||
// No need to push here as we're on a release branch, not master
|
res = childProcess.spawnSync("git", ["rev-parse", "--abbrev-ref", "HEAD"]);
|
||||||
|
let branchName = res.stdout.toString().trim();
|
||||||
|
console.log("Current branch:", branchName);
|
||||||
|
|
||||||
|
// Git push the branch
|
||||||
|
childProcess.spawnSync("git", ["push", "origin", branchName, "--force"], { stdio: "inherit" });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
import * as childProcess from "child_process";
|
import * as childProcess from "child_process";
|
||||||
|
|
||||||
const ignoreList = ["louislam", "CommanderStorm", "UptimeKumaBot", "weblate", "Copilot"];
|
const ignoreList = ["louislam", "CommanderStorm", "UptimeKumaBot", "weblate", "Copilot", "@autofix-ci[bot]"];
|
||||||
|
|
||||||
const mergeList = ["Translations Update from Weblate", "Update dependencies"];
|
const mergeList = ["Translations Update from Weblate", "Update dependencies"];
|
||||||
|
|
||||||
|
|||||||
@ -48,7 +48,7 @@ checkDocker();
|
|||||||
await checkTagExists(repoNames, version);
|
await checkTagExists(repoNames, version);
|
||||||
|
|
||||||
// node extra/beta/update-version.js
|
// node extra/beta/update-version.js
|
||||||
execSync("node ./extra/beta/update-version.js");
|
await import("../beta/update-version.mjs");
|
||||||
|
|
||||||
// Create Pull Request (gh pr create will handle pushing the branch)
|
// Create Pull Request (gh pr create will handle pushing the branch)
|
||||||
await createReleasePR(version, previousVersion, dryRun, branchName, githubRunId);
|
await createReleasePR(version, previousVersion, dryRun, branchName, githubRunId);
|
||||||
|
|||||||
4
package-lock.json
generated
4
package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "uptime-kuma",
|
"name": "uptime-kuma",
|
||||||
"version": "2.1.0-beta.1",
|
"version": "2.1.0-beta.2",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "uptime-kuma",
|
"name": "uptime-kuma",
|
||||||
"version": "2.1.0-beta.1",
|
"version": "2.1.0-beta.2",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@grpc/grpc-js": "~1.8.22",
|
"@grpc/grpc-js": "~1.8.22",
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "uptime-kuma",
|
"name": "uptime-kuma",
|
||||||
"version": "2.1.0-beta.1",
|
"version": "2.1.0-beta.2",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|||||||
@ -176,14 +176,6 @@ class DomainExpiry extends BeanModel {
|
|||||||
|
|
||||||
const rdap = await getRdapServer(tld.publicSuffix);
|
const rdap = await getRdapServer(tld.publicSuffix);
|
||||||
if (!rdap) {
|
if (!rdap) {
|
||||||
// Only warn when the monitor actually has domain expiry notifications enabled.
|
|
||||||
// The edit monitor page calls this method frequently while the user is typing.
|
|
||||||
if (Boolean(monitor.domainExpiryNotification)) {
|
|
||||||
log.warn(
|
|
||||||
"domain_expiry",
|
|
||||||
`Domain expiry unsupported for '.${tld.publicSuffix}' because its RDAP endpoint is not listed in the IANA database.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
throw new TranslatableError("domain_expiry_unsupported_unsupported_tld_no_rdap_endpoint", {
|
throw new TranslatableError("domain_expiry_unsupported_unsupported_tld_no_rdap_endpoint", {
|
||||||
publicSuffix: tld.publicSuffix,
|
publicSuffix: tld.publicSuffix,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1060,7 +1060,15 @@ class Monitor extends BeanModel {
|
|||||||
log.debug("monitor", `Failed getting expiration date for domain ${supportInfo.domain}`);
|
log.debug("monitor", `Failed getting expiration date for domain ${supportInfo.domain}`);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// purposely not logged due to noise. Is accessible via checkMointor
|
if (
|
||||||
|
error.message === "domain_expiry_unsupported_unsupported_tld_no_rdap_endpoint" &&
|
||||||
|
Boolean(this.domainExpiryNotification)
|
||||||
|
) {
|
||||||
|
log.warn(
|
||||||
|
"domain_expiry",
|
||||||
|
`Domain expiry unsupported for '.${error.meta.publicSuffix}' because its RDAP endpoint is not listed in the IANA database.`
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<h4>{{ $t("Certificate Info") }}</h4>
|
<h4>{{ $t("Certificate Info") }}</h4>
|
||||||
{{ $t("Certificate Chain") }}:
|
{{ $t("Certificate Chain:") }}
|
||||||
<div v-if="valid" class="rounded d-inline-flex ms-2 text-white tag-valid">
|
<div v-if="valid" class="rounded d-inline-flex ms-2 text-white tag-valid">
|
||||||
{{ $t("Valid") }}
|
{{ $t("Valid") }}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -40,14 +40,13 @@
|
|||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="form-text">
|
<i18n-t tag="div" keypath="Examples:" class="form-text">
|
||||||
{{ $t("Examples") }}:
|
|
||||||
<ul>
|
<ul>
|
||||||
<li>/var/run/docker.sock</li>
|
<li><code>/var/run/docker.sock</code></li>
|
||||||
<li>http://localhost:2375</li>
|
<li><code>http://localhost:2375</code></li>
|
||||||
<li>https://localhost:2376 (TLS)</li>
|
<li><code>https://localhost:2376 (TLS)</code></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</i18n-t>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -31,12 +31,9 @@
|
|||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="form-text mt-3">
|
<i18n-t tag="div" keypath="Example:" class="form-text mt-3">
|
||||||
{{ $t("Examples") }}:
|
<code>ws://chrome.browserless.io/playwright?token=YOUR-API-TOKEN</code>
|
||||||
<ul>
|
</i18n-t>
|
||||||
<li>ws://chrome.browserless.io/playwright?token=YOUR-API-TOKEN</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -85,12 +85,12 @@ export default {
|
|||||||
|
|
||||||
title() {
|
title() {
|
||||||
if (this.type === "1y") {
|
if (this.type === "1y") {
|
||||||
return `1 ${this.$tc("year", 1)}`;
|
return this.$tc("years", 1, { n: 1 });
|
||||||
}
|
}
|
||||||
if (this.type === "720") {
|
if (this.type === "720") {
|
||||||
return `30 ${this.$tc("day", 30)}`;
|
return this.$tc("days", 30, { n: 30 });
|
||||||
}
|
}
|
||||||
return `24 ${this.$tc("hour", 24)}`;
|
return this.$tc("hours", 24, { n: 24 });
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -2,8 +2,8 @@
|
|||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="octopush-version" class="form-label">{{ $t("Octopush API Version") }}</label>
|
<label for="octopush-version" class="form-label">{{ $t("Octopush API Version") }}</label>
|
||||||
<select id="octopush-version" v-model="$parent.notification.octopushVersion" class="form-select">
|
<select id="octopush-version" v-model="$parent.notification.octopushVersion" class="form-select">
|
||||||
<option value="2">{{ "octopush" }} ({{ $t("endpoint") }}: api.octopush.com)</option>
|
<option value="2">{{ $t("octopushEndpoint", { url: "api.octopush.com" }) }}</option>
|
||||||
<option value="1">{{ $t("Legacy Octopush-DM") }} ({{ $t("endpoint") }}: www.octopush-dm.com)</option>
|
<option value="1">{{ $t("legacyOctopushEndpoint", { url: "www.octopush-dm.com" }) }}</option>
|
||||||
</select>
|
</select>
|
||||||
<div class="form-text">
|
<div class="form-text">
|
||||||
{{ $t("octopushLegacyHint") }}
|
{{ $t("octopushLegacyHint") }}
|
||||||
|
|||||||
@ -27,7 +27,7 @@
|
|||||||
<div class="status">
|
<div class="status">
|
||||||
{{ $t("apiKey-" + item.status) }}
|
{{ $t("apiKey-" + item.status) }}
|
||||||
</div>
|
</div>
|
||||||
<div class="date">{{ $t("Created") }}: {{ item.createdDate }}</div>
|
<div class="date">{{ $t("createdAt", { date: item.createdDate }) }}</div>
|
||||||
<div class="date">
|
<div class="date">
|
||||||
{{ $t("Expires") }}:
|
{{ $t("Expires") }}:
|
||||||
{{ item.expires || $t("Never") }}
|
{{ item.expires || $t("Never") }}
|
||||||
|
|||||||
@ -3,8 +3,8 @@
|
|||||||
<div class="logo d-flex flex-column justify-content-center align-items-center">
|
<div class="logo d-flex flex-column justify-content-center align-items-center">
|
||||||
<object class="my-4" width="200" height="200" data="/icon.svg" />
|
<object class="my-4" width="200" height="200" data="/icon.svg" />
|
||||||
<div class="fs-4 fw-bold">Uptime Kuma</div>
|
<div class="fs-4 fw-bold">Uptime Kuma</div>
|
||||||
<div>{{ $t("Version") }}: {{ $root.info.version }}</div>
|
<div>{{ $t("versionIs", { version: $root.info.version }) }}</div>
|
||||||
<div class="frontend-version">{{ $t("Frontend Version") }}: {{ $root.frontendVersion }}</div>
|
<div class="frontend-version">{{ $t("frontendVersionIs", { version: $root.frontendVersion }) }}</div>
|
||||||
|
|
||||||
<div v-if="!$root.isFrontendBackendVersionMatched" class="alert alert-warning mt-4" role="alert">
|
<div v-if="!$root.isFrontendBackendVersionMatched" class="alert alert-warning mt-4" role="alert">
|
||||||
⚠️ {{ $t("Frontend Version do not match backend version!") }}
|
⚠️ {{ $t("Frontend Version do not match backend version!") }}
|
||||||
|
|||||||
@ -79,7 +79,7 @@
|
|||||||
<ActionInput
|
<ActionInput
|
||||||
v-model="tlsExpiryNotifInput"
|
v-model="tlsExpiryNotifInput"
|
||||||
:type="'number'"
|
:type="'number'"
|
||||||
:placeholder="$t('day')"
|
:placeholder="$tc('days', 1, { n: 1 })"
|
||||||
:icon="'plus'"
|
:icon="'plus'"
|
||||||
:action="() => addTlsExpiryNotifDay(tlsExpiryNotifInput)"
|
:action="() => addTlsExpiryNotifDay(tlsExpiryNotifInput)"
|
||||||
:action-aria-label="$t('Add a new expiry notification day')"
|
:action-aria-label="$t('Add a new expiry notification day')"
|
||||||
@ -117,7 +117,7 @@
|
|||||||
<ActionInput
|
<ActionInput
|
||||||
v-model="domainExpiryNotifInput"
|
v-model="domainExpiryNotifInput"
|
||||||
:type="'number'"
|
:type="'number'"
|
||||||
:placeholder="$t('day')"
|
:placeholder="$tc('days', 1, { n: 1 })"
|
||||||
:icon="'plus'"
|
:icon="'plus'"
|
||||||
:action="() => addDomainExpiryNotifDay(domainExpiryNotifInput)"
|
:action="() => addDomainExpiryNotifDay(domainExpiryNotifInput)"
|
||||||
:action-aria-label="$t('Add a new expiry notification day')"
|
:action-aria-label="$t('Add a new expiry notification day')"
|
||||||
|
|||||||
@ -4,15 +4,13 @@
|
|||||||
<!-- Change Password -->
|
<!-- Change Password -->
|
||||||
<template v-if="!settings.disableAuth">
|
<template v-if="!settings.disableAuth">
|
||||||
<p>
|
<p>
|
||||||
{{ $t("Current User") }}:
|
|
||||||
<strong>{{ $root.username }}</strong>
|
|
||||||
<button
|
<button
|
||||||
v-if="!settings.disableAuth"
|
v-if="!settings.disableAuth"
|
||||||
id="logout-btn"
|
id="logout-btn"
|
||||||
class="btn btn-danger ms-4 me-2 mb-2"
|
class="btn btn-danger ms-4 me-2 mb-2"
|
||||||
@click="$root.logout"
|
@click="$root.logout"
|
||||||
>
|
>
|
||||||
{{ $t("Logout") }}
|
{{ $t("logoutCurrentUser", { username: $root.username }) }}
|
||||||
</button>
|
</button>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|||||||
@ -20,7 +20,7 @@
|
|||||||
"General": "General",
|
"General": "General",
|
||||||
"Game": "Game",
|
"Game": "Game",
|
||||||
"Primary Base URL": "Primary Base URL",
|
"Primary Base URL": "Primary Base URL",
|
||||||
"Version": "Version",
|
"versionIs": "Version: {version}",
|
||||||
"Check Update On GitHub": "Check Update On GitHub",
|
"Check Update On GitHub": "Check Update On GitHub",
|
||||||
"List": "List",
|
"List": "List",
|
||||||
"Home": "Home",
|
"Home": "Home",
|
||||||
@ -55,9 +55,11 @@
|
|||||||
"Monitor": "Monitor | Monitors",
|
"Monitor": "Monitor | Monitors",
|
||||||
"now": "now",
|
"now": "now",
|
||||||
"time ago": "{0} ago",
|
"time ago": "{0} ago",
|
||||||
"day": "day | days",
|
"days": "{n} day | {n} days",
|
||||||
"hour": "hour | hours",
|
"hours": "{n} hour | {n} hours",
|
||||||
"year": "year | years",
|
"minutes": "{n} minute | {n} minutes",
|
||||||
|
"minuteShort": "{n} min | {n} min",
|
||||||
|
"years": "{n} year | {n} years",
|
||||||
"Response": "Response",
|
"Response": "Response",
|
||||||
"Ping": "Ping",
|
"Ping": "Ping",
|
||||||
"Monitor Type": "Monitor Type",
|
"Monitor Type": "Monitor Type",
|
||||||
@ -147,6 +149,7 @@
|
|||||||
"where you intend to implement third-party authentication": "where you intend to implement third-party authentication",
|
"where you intend to implement third-party authentication": "where you intend to implement third-party authentication",
|
||||||
"Please use this option carefully!": "Please use this option carefully!",
|
"Please use this option carefully!": "Please use this option carefully!",
|
||||||
"Logout": "Log out",
|
"Logout": "Log out",
|
||||||
|
"logoutCurrentUser": "Log out {username}",
|
||||||
"Leave": "Leave",
|
"Leave": "Leave",
|
||||||
"I understand, please disable": "I understand, please disable",
|
"I understand, please disable": "I understand, please disable",
|
||||||
"Confirm": "Confirm",
|
"Confirm": "Confirm",
|
||||||
@ -282,7 +285,6 @@
|
|||||||
"records": "records",
|
"records": "records",
|
||||||
"One record": "One record",
|
"One record": "One record",
|
||||||
"steamApiKeyDescriptionAt": "For monitoring a Steam Game Server you need a Steam Web-API key. You can register your API key at {url}",
|
"steamApiKeyDescriptionAt": "For monitoring a Steam Game Server you need a Steam Web-API key. You can register your API key at {url}",
|
||||||
"Current User": "Current User",
|
|
||||||
"topic": "Topic",
|
"topic": "Topic",
|
||||||
"topicExplanation": "MQTT topic to monitor",
|
"topicExplanation": "MQTT topic to monitor",
|
||||||
"mqttWebSocketPath": "MQTT WebSocket Path",
|
"mqttWebSocketPath": "MQTT WebSocket Path",
|
||||||
@ -322,8 +324,9 @@
|
|||||||
"dark": "dark",
|
"dark": "dark",
|
||||||
"Post": "Post",
|
"Post": "Post",
|
||||||
"Please input title and content": "Please input title and content",
|
"Please input title and content": "Please input title and content",
|
||||||
"Created": "Created",
|
"createdAt": "Created: {date}",
|
||||||
"Last Updated": "Last Updated",
|
"lastUpdatedAt": "Last Updated: {date}",
|
||||||
|
"lastUpdatedAtFromNow": "Last Updated: {date} ({fromNow})",
|
||||||
"Switch to Light Theme": "Switch to Light Theme",
|
"Switch to Light Theme": "Switch to Light Theme",
|
||||||
"Switch to Dark Theme": "Switch to Dark Theme",
|
"Switch to Dark Theme": "Switch to Dark Theme",
|
||||||
"Show Tags": "Show Tags",
|
"Show Tags": "Show Tags",
|
||||||
@ -358,7 +361,7 @@
|
|||||||
"proxyDescription": "Proxies must be assigned to a monitor to function.",
|
"proxyDescription": "Proxies must be assigned to a monitor to function.",
|
||||||
"enableProxyDescription": "This proxy will not effect on monitor requests until it is activated. You can control temporarily disable the proxy from all monitors by activation status.",
|
"enableProxyDescription": "This proxy will not effect on monitor requests until it is activated. You can control temporarily disable the proxy from all monitors by activation status.",
|
||||||
"setAsDefaultProxyDescription": "This proxy will be enabled by default for new monitors. You can still disable the proxy separately for each monitor.",
|
"setAsDefaultProxyDescription": "This proxy will be enabled by default for new monitors. You can still disable the proxy separately for each monitor.",
|
||||||
"Certificate Chain": "Certificate Chain",
|
"Certificate Chain:": "Certificate Chain:",
|
||||||
"Valid": "Valid",
|
"Valid": "Valid",
|
||||||
"Invalid": "Invalid",
|
"Invalid": "Invalid",
|
||||||
"User": "User",
|
"User": "User",
|
||||||
@ -405,7 +408,7 @@
|
|||||||
"Add a new expiry notification day": "Add a new expiry notification day",
|
"Add a new expiry notification day": "Add a new expiry notification day",
|
||||||
"Remove the expiry notification": "Remove the expiry notification day",
|
"Remove the expiry notification": "Remove the expiry notification day",
|
||||||
"Proxy": "Proxy",
|
"Proxy": "Proxy",
|
||||||
"Date Created": "Date Created",
|
"dateCreatedAtFromNow": "Date Created: {date} ({fromNow})",
|
||||||
"Footer Text": "Footer Text",
|
"Footer Text": "Footer Text",
|
||||||
"RSS Title": "RSS Title",
|
"RSS Title": "RSS Title",
|
||||||
"Leave blank to use status page title": "Leave blank to use status page title",
|
"Leave blank to use status page title": "Leave blank to use status page title",
|
||||||
@ -479,7 +482,7 @@
|
|||||||
"disableCloudflaredNoAuthMsg": "You are in No Auth mode, a password is not required.",
|
"disableCloudflaredNoAuthMsg": "You are in No Auth mode, a password is not required.",
|
||||||
"trustProxyDescription": "Trust 'X-Forwarded-*' headers. If you want to get the correct client IP and your Uptime Kuma is behind a proxy such as Nginx or Apache, you should enable this.",
|
"trustProxyDescription": "Trust 'X-Forwarded-*' headers. If you want to get the correct client IP and your Uptime Kuma is behind a proxy such as Nginx or Apache, you should enable this.",
|
||||||
"wayToGetLineNotifyToken": "You can get an access token from {0}",
|
"wayToGetLineNotifyToken": "You can get an access token from {0}",
|
||||||
"Examples": "Examples",
|
"Examples:": "Examples: {0}",
|
||||||
"supportBaleChatID": "Support Direct Chat / Group / Channel's Chat ID",
|
"supportBaleChatID": "Support Direct Chat / Group / Channel's Chat ID",
|
||||||
"wayToGetBaleChatID": "You can get your chat ID by sending a message to the bot and going to this URL to view the chat_id:",
|
"wayToGetBaleChatID": "You can get your chat ID by sending a message to the bot and going to this URL to view the chat_id:",
|
||||||
"wayToGetBaleToken": "You can get a token from {0}.",
|
"wayToGetBaleToken": "You can get a token from {0}.",
|
||||||
@ -494,7 +497,7 @@
|
|||||||
"Event type:": "Event type:",
|
"Event type:": "Event type:",
|
||||||
"Event data:": "Event data:",
|
"Event data:": "Event data:",
|
||||||
"Then choose an action, for example switch the scene to where an RGB light is red.": "Then choose an action, for example switch the scene to where an RGB light is red.",
|
"Then choose an action, for example switch the scene to where an RGB light is red.": "Then choose an action, for example switch the scene to where an RGB light is red.",
|
||||||
"Frontend Version": "Frontend Version",
|
"frontendVersionIs": "Frontend Version: {version}",
|
||||||
"Frontend Version do not match backend version!": "Frontend Version do not match backend version!",
|
"Frontend Version do not match backend version!": "Frontend Version do not match backend version!",
|
||||||
"backupOutdatedWarning": "Deprecated: Since a lot of features were added and this backup feature is a bit unmaintained, it cannot generate or restore a complete backup.",
|
"backupOutdatedWarning": "Deprecated: Since a lot of features were added and this backup feature is a bit unmaintained, it cannot generate or restore a complete backup.",
|
||||||
"backupRecommend": "Please backup the volume or the data folder (./data/) directly instead.",
|
"backupRecommend": "Please backup the volume or the data folder (./data/) directly instead.",
|
||||||
@ -505,7 +508,7 @@
|
|||||||
"startDateTime": "Start Date/Time",
|
"startDateTime": "Start Date/Time",
|
||||||
"endDateTime": "End Date/Time",
|
"endDateTime": "End Date/Time",
|
||||||
"cronExpression": "Cron Expression",
|
"cronExpression": "Cron Expression",
|
||||||
"cronSchedule": "Schedule: ",
|
"cronScheduleDescription": "Schedule: {description}",
|
||||||
"Duration (Minutes)": "Duration (Minutes)",
|
"Duration (Minutes)": "Duration (Minutes)",
|
||||||
"invalidCronExpression": "Invalid Cron Expression: {0}",
|
"invalidCronExpression": "Invalid Cron Expression: {0}",
|
||||||
"recurringInterval": "Interval",
|
"recurringInterval": "Interval",
|
||||||
@ -672,6 +675,8 @@
|
|||||||
"recurringIntervalMessage": "Run once every day | Run once every {0} days",
|
"recurringIntervalMessage": "Run once every day | Run once every {0} days",
|
||||||
"affectedMonitorsDescription": "Select monitors that are affected by current maintenance",
|
"affectedMonitorsDescription": "Select monitors that are affected by current maintenance",
|
||||||
"affectedStatusPages": "Show this maintenance message on selected status pages",
|
"affectedStatusPages": "Show this maintenance message on selected status pages",
|
||||||
|
"Sets end time based on start time": "Sets end time based on start time",
|
||||||
|
"Please set start time first": "Please set start time first",
|
||||||
"noMonitorsSelectedWarning": "You are creating a maintenance without any affected monitors. Are you sure you want to continue?",
|
"noMonitorsSelectedWarning": "You are creating a maintenance without any affected monitors. Are you sure you want to continue?",
|
||||||
"noMonitorsOrStatusPagesSelectedError": "Cannot create maintenance without affected monitors or status pages",
|
"noMonitorsOrStatusPagesSelectedError": "Cannot create maintenance without affected monitors or status pages",
|
||||||
"passwordNotMatchMsg": "The repeat password does not match.",
|
"passwordNotMatchMsg": "The repeat password does not match.",
|
||||||
@ -682,7 +687,6 @@
|
|||||||
"backupDescription": "You can backup all monitors and notifications into a JSON file.",
|
"backupDescription": "You can backup all monitors and notifications into a JSON file.",
|
||||||
"backupDescription2": "Note: history and event data is not included.",
|
"backupDescription2": "Note: history and event data is not included.",
|
||||||
"backupDescription3": "Sensitive data such as notification tokens are included in the export file; please store export securely.",
|
"backupDescription3": "Sensitive data such as notification tokens are included in the export file; please store export securely.",
|
||||||
"endpoint": "endpoint",
|
|
||||||
"octopushAPIKey": "\"API key\" from HTTP API credentials in control panel",
|
"octopushAPIKey": "\"API key\" from HTTP API credentials in control panel",
|
||||||
"octopushLogin": "\"Login\" from HTTP API credentials in control panel",
|
"octopushLogin": "\"Login\" from HTTP API credentials in control panel",
|
||||||
"promosmsLogin": "API Login Name",
|
"promosmsLogin": "API Login Name",
|
||||||
@ -884,7 +888,8 @@
|
|||||||
"From Name/Number": "From Name/Number",
|
"From Name/Number": "From Name/Number",
|
||||||
"Leave blank to use a shared sender number.": "Leave blank to use a shared sender number.",
|
"Leave blank to use a shared sender number.": "Leave blank to use a shared sender number.",
|
||||||
"Octopush API Version": "Octopush API Version",
|
"Octopush API Version": "Octopush API Version",
|
||||||
"Legacy Octopush-DM": "Legacy Octopush-DM",
|
"octopushEndpoint": "octopush (endpoint: {url})",
|
||||||
|
"legacyOctopushEndpoint": "Legacy Octopush-DM (endpoint: {url})",
|
||||||
"ntfy Topic": "ntfy Topic",
|
"ntfy Topic": "ntfy Topic",
|
||||||
"Server URL should not contain the nfty topic": "Server URL should not contain the nfty topic",
|
"Server URL should not contain the nfty topic": "Server URL should not contain the nfty topic",
|
||||||
"onebotHttpAddress": "OneBot HTTP Address",
|
"onebotHttpAddress": "OneBot HTTP Address",
|
||||||
|
|||||||
@ -123,16 +123,13 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Single Maintenance Window -->
|
|
||||||
<template v-if="maintenance.strategy === 'single'"></template>
|
|
||||||
|
|
||||||
<template v-if="maintenance.strategy === 'cron'">
|
<template v-if="maintenance.strategy === 'cron'">
|
||||||
<!-- Cron -->
|
<!-- Cron -->
|
||||||
<div class="my-3">
|
<div class="my-3">
|
||||||
<label for="cron" class="form-label">
|
<label for="cron" class="form-label">
|
||||||
{{ $t("cronExpression") }}
|
{{ $t("cronExpression") }}
|
||||||
</label>
|
</label>
|
||||||
<p>{{ $t("cronSchedule") }}{{ cronDescription }}</p>
|
<p>{{ $t("cronScheduleDescription", { description: cronDescription }) }}</p>
|
||||||
<input
|
<input
|
||||||
id="cron"
|
id="cron"
|
||||||
v-model="maintenance.cron"
|
v-model="maintenance.cron"
|
||||||
@ -331,6 +328,102 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<template v-if="maintenance.strategy === 'single'">
|
||||||
|
<div class="my-3">
|
||||||
|
<div class="d-flex gap-2 flex-wrap">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-sm"
|
||||||
|
:class="
|
||||||
|
currentDurationMinutes === 15 ? 'btn-primary' : 'btn-outline-primary'
|
||||||
|
"
|
||||||
|
:disabled="currentDurationMinutes === 15"
|
||||||
|
@click="setQuickDuration(15)"
|
||||||
|
>
|
||||||
|
{{ $tc("minuteShort", 15, { n: 15 }) }}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-sm"
|
||||||
|
:class="
|
||||||
|
currentDurationMinutes === 30 ? 'btn-primary' : 'btn-outline-primary'
|
||||||
|
"
|
||||||
|
:disabled="currentDurationMinutes === 30"
|
||||||
|
@click="setQuickDuration(30)"
|
||||||
|
>
|
||||||
|
{{ $tc("minuteShort", 30, { n: 30 }) }}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-sm"
|
||||||
|
:class="
|
||||||
|
currentDurationMinutes === 60 ? 'btn-primary' : 'btn-outline-primary'
|
||||||
|
"
|
||||||
|
:disabled="currentDurationMinutes === 60"
|
||||||
|
@click="setQuickDuration(60)"
|
||||||
|
>
|
||||||
|
{{ $tc("hours", 1, { n: 1 }) }}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-sm"
|
||||||
|
:class="
|
||||||
|
currentDurationMinutes === 120 ? 'btn-primary' : 'btn-outline-primary'
|
||||||
|
"
|
||||||
|
:disabled="currentDurationMinutes === 120"
|
||||||
|
@click="setQuickDuration(120)"
|
||||||
|
>
|
||||||
|
{{ $tc("hours", 2, { n: 2 }) }}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-sm"
|
||||||
|
:class="
|
||||||
|
currentDurationMinutes === 240 ? 'btn-primary' : 'btn-outline-primary'
|
||||||
|
"
|
||||||
|
:disabled="currentDurationMinutes === 240"
|
||||||
|
@click="setQuickDuration(240)"
|
||||||
|
>
|
||||||
|
{{ $tc("hours", 4, { n: 4 }) }}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-sm"
|
||||||
|
:class="
|
||||||
|
currentDurationMinutes === 480 ? 'btn-primary' : 'btn-outline-primary'
|
||||||
|
"
|
||||||
|
:disabled="currentDurationMinutes === 480"
|
||||||
|
@click="setQuickDuration(480)"
|
||||||
|
>
|
||||||
|
{{ $tc("hours", 8, { n: 8 }) }}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-sm"
|
||||||
|
:class="
|
||||||
|
currentDurationMinutes === 720 ? 'btn-primary' : 'btn-outline-primary'
|
||||||
|
"
|
||||||
|
:disabled="currentDurationMinutes === 720"
|
||||||
|
@click="setQuickDuration(720)"
|
||||||
|
>
|
||||||
|
{{ $tc("hours", 12, { n: 12 }) }}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-sm"
|
||||||
|
:class="
|
||||||
|
currentDurationMinutes === 1440 ? 'btn-primary' : 'btn-outline-primary'
|
||||||
|
"
|
||||||
|
:disabled="currentDurationMinutes === 1440"
|
||||||
|
@click="setQuickDuration(1440)"
|
||||||
|
>
|
||||||
|
{{ $tc("hours", 24, { n: 24 }) }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="form-text">{{ $t("Sets end time based on start time") }}</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -511,6 +604,22 @@ export default {
|
|||||||
hasStatusPages() {
|
hasStatusPages() {
|
||||||
return this.showOnAllPages || this.selectedStatusPages.length > 0;
|
return this.showOnAllPages || this.selectedStatusPages.length > 0;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the current duration in minutes between start and end dates
|
||||||
|
* @returns {number|null} Duration in minutes, or null if dates are invalid
|
||||||
|
*/
|
||||||
|
currentDurationMinutes() {
|
||||||
|
if (!this.maintenance.dateRange?.[0] || !this.maintenance.dateRange?.[1]) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const start = new Date(this.maintenance.dateRange[0]);
|
||||||
|
const end = new Date(this.maintenance.dateRange[1]);
|
||||||
|
if (isNaN(start.getTime()) || isNaN(end.getTime())) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return Math.round((end.getTime() - start.getTime()) / 60000);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
"$route.fullPath"() {
|
"$route.fullPath"() {
|
||||||
@ -570,6 +679,19 @@ export default {
|
|||||||
this.selectedStatusPages = [];
|
this.selectedStatusPages = [];
|
||||||
|
|
||||||
if (this.isAdd) {
|
if (this.isAdd) {
|
||||||
|
// Get current date/time in local timezone
|
||||||
|
const now = new Date();
|
||||||
|
const oneHourLater = new Date(now.getTime() + 60 * 60000);
|
||||||
|
|
||||||
|
const formatDateTime = (date) => {
|
||||||
|
const year = date.getFullYear();
|
||||||
|
const month = String(date.getMonth() + 1).padStart(2, "0");
|
||||||
|
const day = String(date.getDate()).padStart(2, "0");
|
||||||
|
const hours = String(date.getHours()).padStart(2, "0");
|
||||||
|
const minutes = String(date.getMinutes()).padStart(2, "0");
|
||||||
|
return `${year}-${month}-${day}T${hours}:${minutes}`;
|
||||||
|
};
|
||||||
|
|
||||||
this.maintenance = {
|
this.maintenance = {
|
||||||
title: "",
|
title: "",
|
||||||
description: "",
|
description: "",
|
||||||
@ -578,7 +700,7 @@ export default {
|
|||||||
cron: "30 3 * * *",
|
cron: "30 3 * * *",
|
||||||
durationMinutes: 60,
|
durationMinutes: 60,
|
||||||
intervalDay: 1,
|
intervalDay: 1,
|
||||||
dateRange: [],
|
dateRange: [formatDateTime(now), formatDateTime(oneHourLater)],
|
||||||
timeRange: [
|
timeRange: [
|
||||||
{
|
{
|
||||||
hours: 2,
|
hours: 2,
|
||||||
@ -591,7 +713,7 @@ export default {
|
|||||||
],
|
],
|
||||||
weekdays: [],
|
weekdays: [],
|
||||||
daysOfMonth: [],
|
daysOfMonth: [],
|
||||||
timezoneOption: null,
|
timezoneOption: "SAME_AS_SERVER",
|
||||||
};
|
};
|
||||||
} else if (this.isEdit || this.isClone) {
|
} else if (this.isEdit || this.isClone) {
|
||||||
this.$root.getSocket().emit("getMaintenance", this.$route.params.id, (res) => {
|
this.$root.getSocket().emit("getMaintenance", this.$route.params.id, (res) => {
|
||||||
@ -655,6 +777,30 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set quick duration for single maintenance
|
||||||
|
* Calculates end time based on start time + duration in minutes
|
||||||
|
* @param {number} minutes Duration in minutes
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
setQuickDuration(minutes) {
|
||||||
|
if (!this.maintenance.dateRange[0]) {
|
||||||
|
this.$root.toastError(this.$t("Please set start time first"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const startDate = new Date(this.maintenance.dateRange[0]);
|
||||||
|
const endDate = new Date(startDate.getTime() + minutes * 60000);
|
||||||
|
|
||||||
|
const year = endDate.getFullYear();
|
||||||
|
const month = String(endDate.getMonth() + 1).padStart(2, "0");
|
||||||
|
const day = String(endDate.getDate()).padStart(2, "0");
|
||||||
|
const hours = String(endDate.getHours()).padStart(2, "0");
|
||||||
|
const mins = String(endDate.getMinutes()).padStart(2, "0");
|
||||||
|
|
||||||
|
this.maintenance.dateRange[1] = `${year}-${month}-${day}T${hours}:${mins}`;
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle form submission - show confirmation if no monitors selected
|
* Handle form submission - show confirmation if no monitors selected
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
|
|||||||
@ -342,14 +342,20 @@
|
|||||||
|
|
||||||
<!-- Incident Date -->
|
<!-- Incident Date -->
|
||||||
<div class="date mt-3">
|
<div class="date mt-3">
|
||||||
{{ $t("Date Created") }}: {{ $root.datetime(incident.createdDate) }} ({{
|
{{
|
||||||
dateFromNow(incident.createdDate)
|
$t("dateCreatedAtFromNow", {
|
||||||
}})
|
date: $root.datetime(incident.createdDate),
|
||||||
|
fromNow: dateFromNow(incident.createdDate),
|
||||||
|
})
|
||||||
|
}}
|
||||||
<br />
|
<br />
|
||||||
<span v-if="incident.lastUpdatedDate">
|
<span v-if="incident.lastUpdatedDate">
|
||||||
{{ $t("Last Updated") }}: {{ $root.datetime(incident.lastUpdatedDate) }} ({{
|
{{
|
||||||
dateFromNow(incident.lastUpdatedDate)
|
$t("lastUpdatedAtFromNow", {
|
||||||
}})
|
date: $root.datetime(incident.lastUpdatedDate),
|
||||||
|
fromNow: dateFromNow(incident.lastUpdatedDate),
|
||||||
|
})
|
||||||
|
}}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -572,7 +578,7 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="refresh-info mb-2">
|
<div class="refresh-info mb-2">
|
||||||
<div>{{ $t("Last Updated") }}: {{ lastUpdateTimeDisplay }}</div>
|
<div>{{ $t("lastUpdatedAt", { date: lastUpdateTimeDisplay }) }}</div>
|
||||||
<div data-testid="update-countdown-text">
|
<div data-testid="update-countdown-text">
|
||||||
{{ $tc("statusPageRefreshIn", [updateCountdownText]) }}
|
{{ $tc("statusPageRefreshIn", [updateCountdownText]) }}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user