Merge remote-tracking branch 'origin/master' into karelkryda_master

# Conflicts:
#	src/components/HeartbeatBar.vue
This commit is contained in:
Louis Lam 2022-05-18 19:49:54 +08:00
commit 7a46b44d25
23 changed files with 307 additions and 88 deletions

View file

@ -1,6 +1,6 @@
{ {
"name": "uptime-kuma", "name": "uptime-kuma",
"version": "1.15.1", "version": "1.16.0-beta.0",
"license": "MIT", "license": "MIT",
"repository": { "repository": {
"type": "git", "type": "git",

View file

@ -185,7 +185,7 @@ class Monitor extends BeanModel {
// undefined if not https // undefined if not https
let tlsInfo = undefined; let tlsInfo = undefined;
if (!previousBeat) { if (!previousBeat || this.type === "push") {
previousBeat = await R.findOne("heartbeat", " monitor_id = ? ORDER BY time DESC", [ previousBeat = await R.findOne("heartbeat", " monitor_id = ? ORDER BY time DESC", [
this.id, this.id,
]); ]);
@ -383,9 +383,6 @@ class Monitor extends BeanModel {
log.debug("monitor", "heartbeatCount" + heartbeatCount + " " + time); log.debug("monitor", "heartbeatCount" + heartbeatCount + " " + time);
if (heartbeatCount <= 0) { if (heartbeatCount <= 0) {
// Fix #922, since previous heartbeat could be inserted by api, it should get from database
previousBeat = await Monitor.getPreviousHeartbeat(this.id);
throw new Error("No heartbeat in the time window"); throw new Error("No heartbeat in the time window");
} else { } else {
// No need to insert successful heartbeat for push type, so end here // No need to insert successful heartbeat for push type, so end here

View file

@ -6,9 +6,14 @@ class Apprise extends NotificationProvider {
name = "apprise"; name = "apprise";
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
let s = childProcess.spawnSync("apprise", [ "-vv", "-b", msg, notification.appriseURL ]); const args = [ "-vv", "-b", msg, notification.appriseURL ];
if (notification.title) {
args.push("-t");
args.push(notification.title);
}
const s = childProcess.spawnSync("apprise", args);
let output = (s.stdout) ? s.stdout.toString() : "ERROR: maybe apprise not found"; const output = (s.stdout) ? s.stdout.toString() : "ERROR: maybe apprise not found";
if (output) { if (output) {

View file

@ -22,16 +22,23 @@ class Discord extends NotificationProvider {
return okMsg; return okMsg;
} }
let url; let address;
if (monitorJSON["type"] === "port") { switch (monitorJSON["type"]) {
url = monitorJSON["hostname"]; case "ping":
address = monitorJSON["hostname"];
break;
case "port":
case "dns":
case "steam":
address = monitorJSON["hostname"];
if (monitorJSON["port"]) { if (monitorJSON["port"]) {
url += ":" + monitorJSON["port"]; address += ":" + monitorJSON["port"];
} }
break;
} else { default:
url = monitorJSON["url"]; address = monitorJSON["url"];
break;
} }
// If heartbeatJSON is not null, we go into the normal alerting loop. // If heartbeatJSON is not null, we go into the normal alerting loop.
@ -48,8 +55,8 @@ class Discord extends NotificationProvider {
value: monitorJSON["name"], value: monitorJSON["name"],
}, },
{ {
name: "Service URL", name: "Service URL / Address",
value: url, value: address,
}, },
{ {
name: "Time (UTC)", name: "Time (UTC)",
@ -84,7 +91,7 @@ class Discord extends NotificationProvider {
}, },
{ {
name: "Service URL", name: "Service URL",
value: url.startsWith("http") ? "[Visit Service](" + url + ")" : url, value: address.startsWith("http") ? "[Visit Service](" + address + ")" : address,
}, },
{ {
name: "Time (UTC)", name: "Time (UTC)",

View file

@ -368,7 +368,7 @@ textarea.form-control {
.item { .item {
display: block; display: block;
text-decoration: none; text-decoration: none;
padding: 13px 15px 10px 15px; padding: 15px;
border-radius: 10px; border-radius: 10px;
transition: all ease-in-out 0.15s; transition: all ease-in-out 0.15s;

View file

@ -1,6 +1,6 @@
<template> <template>
<div ref="wrap" class="wrap" :style="wrapStyle"> <div ref="wrap" class="wrap" :style="wrapStyle">
<div class="hp-bar-big" :style="barStyle"> <div class="hp-bar-big d-flex" :style="barStyle">
<div <div
v-for="(beat, index) in shortBeatList" v-for="(beat, index) in shortBeatList"
:key="index" :key="index"
@ -8,7 +8,11 @@
:class="{ 'empty' : (beat === 0), 'down' : (beat.status === 0), 'pending' : (beat.status === 2), 'maintenance' : (beat.status === 3) }" :class="{ 'empty' : (beat === 0), 'down' : (beat.status === 0), 'pending' : (beat.status === 2), 'maintenance' : (beat.status === 3) }"
:style="beatStyle" :style="beatStyle"
:title="getBeatTitle(beat)" :title="getBeatTitle(beat)"
/> @mouseenter="toggleActivateSibling"
@mouseleave="toggleActivateSibling"
>
<div class="beat-inner" />
</div>
</div> </div>
</div> </div>
</template> </template>
@ -168,6 +172,29 @@ export default {
getBeatTitle(beat) { getBeatTitle(beat) {
return `${this.$root.datetime(beat.time)}` + ((beat.msg) ? ` - ${beat.msg}` : ""); return `${this.$root.datetime(beat.time)}` + ((beat.msg) ? ` - ${beat.msg}` : "");
},
// Toggling the activeSibling class on hover over the current hover item
toggleActivateSibling(e) {
// Variable definition
const element = e.target;
const previous = element.previousSibling;
const next = element.nextSibling;
// Return if the hovered element has empty class
if (element.classList.contains("empty")) {
return;
}
// Check if Previous Sibling is heartbar element and doesn't have the empty class
if (previous.children && !previous.classList.contains("empty")) {
previous.classList.toggle("active-sibling");
}
// Check if Next Sibling is heartbar element and doesn't have the empty class
if (next.children && !next.classList.contains("empty")) {
next.classList.toggle("active-sibling");
}
} }
}, },
}; };
@ -184,9 +211,10 @@ export default {
.hp-bar-big { .hp-bar-big {
.beat { .beat {
display: inline-block;
background-color: $primary; background-color: $primary;
border-radius: $border-radius; border-radius: $border-radius;
display: inline-block;
transition: all ease 0.6s;
&.empty { &.empty {
background-color: aliceblue; background-color: aliceblue;
@ -200,15 +228,27 @@ export default {
background-color: $warning; background-color: $warning;
} }
.beat-inner {
border-radius: $border-radius;
display: inline-block;
height: 100%;
width: 5px;
}
&.maintenance { &.maintenance {
background-color: $maintenance; background-color: $maintenance;
} }
&:not(.empty):hover { &:not(.empty):hover {
transition: all ease-in-out 0.15s; transition: all ease 0.15s;
opacity: 0.8; opacity: 0.8;
transform: scale(var(--hover-scale)); transform: scale(var(--hover-scale));
} }
&.active-sibling {
transform: scale(1.15);
transition: all ease 0.15s;
}
} }
} }

View file

@ -33,19 +33,19 @@
<template #item="monitor"> <template #item="monitor">
<div class="item"> <div class="item">
<div class="row"> <div class="row">
<div class="col-9 col-md-8 small-padding"> <div class="col-9 col-md-8 small-padding d-flex align-items-center flex-wrap">
<div class="info"> <div class="info d-flex align-items-center gap-3 w-100">
<font-awesome-icon v-if="editMode" icon="arrows-alt-v" class="action drag me-3" /> <font-awesome-icon v-if="editMode" icon="arrows-alt-v" class="action drag me-3" />
<font-awesome-icon v-if="editMode" icon="times" class="action remove me-3" @click="removeMonitor(group.index, monitor.index)" /> <font-awesome-icon v-if="editMode" icon="times" class="action remove me-3" @click="removeMonitor(group.index, monitor.index)" />
<Uptime :monitor="monitor.element" type="24" :pill="true" /> <Uptime :monitor="monitor.element" type="24" :pill="true" />
{{ monitor.element.name }} {{ monitor.element.name }}
</div> </div>
<div v-if="showTags" class="tags"> <div v-if="showTags && monitor.element.tags.length > 0" class="tags">
<Tag v-for="tag in monitor.element.tags" :key="tag" :item="tag" :size="'sm'" /> <Tag v-for="tag in monitor.element.tags" :key="tag" :item="tag" :size="'sm'" />
</div> </div>
</div> </div>
<div :key="$root.userHeartbeatBar" class="col-3 col-md-4"> <div :key="$root.userHeartbeatBar" class="col-3 col-md-4 d-flex align-items-center">
<HeartbeatBar size="small" :monitor-id="monitor.element.id" /> <HeartbeatBar size="small" :monitor-id="monitor.element.id" />
</div> </div>
</div> </div>

View file

@ -8,6 +8,9 @@
<a href="https://github.com/caronc/apprise/wiki#notification-services" target="_blank">https://github.com/caronc/apprise/wiki#notification-services</a> <a href="https://github.com/caronc/apprise/wiki#notification-services" target="_blank">https://github.com/caronc/apprise/wiki#notification-services</a>
</i18n-t> </i18n-t>
</div> </div>
<label for="title" class="form-label">{{ $t("Title") }}</label>
<input id="title" v-model="$parent.notification.title" type="text" class="form-control">
</div> </div>
<div class="mb-3"> <div class="mb-3">
<i18n-t tag="p" keypath="Status:"> <i18n-t tag="p" keypath="Status:">

View file

@ -1,12 +1,11 @@
<template> <template>
<div class="mb-3"> <div class="mb-3">
<label for="clicksendsms-login" class="form-label">API Username</label> <label for="clicksendsms-login" class="form-label">{{ $t("API Username") }}</label>
<div class="form-text"> <i18n-t tag="div" class="form-text" keypath="wayToGetClickSendSMSToken">
{{ $t("apiCredentials") }}
<a href="http://dashboard.clicksend.com/account/subaccounts" target="_blank">{{ $t("here") }}</a> <a href="http://dashboard.clicksend.com/account/subaccounts" target="_blank">{{ $t("here") }}</a>
</div> </i18n-t>
<input id="clicksendsms-login" v-model="$parent.notification.clicksendsmsLogin" type="text" class="form-control" required> <input id="clicksendsms-login" v-model="$parent.notification.clicksendsmsLogin" type="text" class="form-control" required>
<label for="clicksendsms-key" class="form-label">API Key</label> <label for="clicksendsms-key" class="form-label">{{ $t("API Key") }}</label>
<HiddenInput id="clicksendsms-key" v-model="$parent.notification.clicksendsmsPassword" :required="true" autocomplete="one-time-code"></HiddenInput> <HiddenInput id="clicksendsms-key" v-model="$parent.notification.clicksendsmsPassword" :required="true" autocomplete="one-time-code"></HiddenInput>
</div> </div>
<div class="mb-3"> <div class="mb-3">
@ -16,15 +15,15 @@
</div> </div>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label for="clicksendsms-to-number" class="form-label">Recipient Number</label> <label for="clicksendsms-to-number" class="form-label">{{ $t("Recipient Number") }}</label>
<input id="clicksendsms-to-number" v-model="$parent.notification.clicksendsmsToNumber" type="text" minlength="8" maxlength="14" class="form-control" required> <input id="clicksendsms-to-number" v-model="$parent.notification.clicksendsmsToNumber" type="text" minlength="8" maxlength="14" class="form-control" required>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label for="clicksendsms-sender-name" class="form-label">From Name/Number - <label for="clicksendsms-sender-name" class="form-label">{{ $t("From Name/Number") }} -
<a href="https://help.clicksend.com/article/4kgj7krx00-what-is-a-sender-id-or-sender-number" target="_blank">More Info</a> <a href="https://help.clicksend.com/article/4kgj7krx00-what-is-a-sender-id-or-sender-number" target="_blank">{{ $t("Read more") }}</a>
</label> </label>
<input id="clicksendsms-sender-name" v-model="$parent.notification.clicksendsmsSenderName" type="text" minlength="3" maxlength="11" class="form-control"> <input id="clicksendsms-sender-name" v-model="$parent.notification.clicksendsmsSenderName" type="text" minlength="3" maxlength="11" class="form-control">
<div class="form-text">Leave blank to use a shared sender number.</div> <div class="form-text">{{ $t("Leave blank to use a shared sender number.") }}</div>
</div> </div>
</template> </template>
<script> <script>

View file

@ -7,7 +7,7 @@
<b>{{ $t("Basic Settings") }}</b> <b>{{ $t("Basic Settings") }}</b>
</i18n-t> </i18n-t>
<div class="mb-3" style="margin-top: 12px;"> <div class="mb-3" style="margin-top: 12px;">
<label for="line-user-id" class="form-label">User ID</label> <label for="line-user-id" class="form-label">{{ $t("User ID") }}</label>
<input id="line-user-id" v-model="$parent.notification.lineUserID" type="text" class="form-control" required> <input id="line-user-id" v-model="$parent.notification.lineUserID" type="text" class="form-control" required>
</div> </div>
<i18n-t tag="div" keypath="lineDevConsoleTo" class="form-text"> <i18n-t tag="div" keypath="lineDevConsoleTo" class="form-text">

View file

@ -1,18 +1,18 @@
<template> <template>
<div class="mb-3"> <div class="mb-3">
<label for="octopush-version" class="form-label">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 (endpoint: api.octopush.com)</option> <option value="2">{{ $t("octopush") }} ({{ $t("endpoint") }}: api.octopush.com)</option>
<option value="1">Legacy Octopush-DM (endpoint: www.octopush-dm.com)</option> <option value="1">{{ $t("Legacy Octopush-DM") }} ({{ $t("endpoint") }}: www.octopush-dm.com)</option>
</select> </select>
<div class="form-text"> <div class="form-text">
{{ $t("octopushLegacyHint") }} {{ $t("octopushLegacyHint") }}
</div> </div>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label for="octopush-key" class="form-label">API KEY</label> <label for="octopush-key" class="form-label">{{ $t("octopushAPIKey") }}</label>
<HiddenInput id="octopush-key" v-model="$parent.notification.octopushAPIKey" :required="true" autocomplete="one-time-code"></HiddenInput> <HiddenInput id="octopush-key" v-model="$parent.notification.octopushAPIKey" :required="true" autocomplete="one-time-code"></HiddenInput>
<label for="octopush-login" class="form-label">API LOGIN</label> <label for="octopush-login" class="form-label">{{ $t("octopushLogin") }}</label>
<input id="octopush-login" v-model="$parent.notification.octopushLogin" type="text" class="form-control" required> <input id="octopush-login" v-model="$parent.notification.octopushLogin" type="text" class="form-control" required>
</div> </div>
<div class="mb-3"> <div class="mb-3">

View file

@ -1,8 +1,8 @@
<template> <template>
<div class="mb-3"> <div class="mb-3">
<label for="promosms-login" class="form-label">API LOGIN</label> <label for="promosms-login" class="form-label">{{ $("promosmsLogin") }}</label>
<input id="promosms-login" v-model="$parent.notification.promosmsLogin" type="text" class="form-control" required> <input id="promosms-login" v-model="$parent.notification.promosmsLogin" type="text" class="form-control" required>
<label for="promosms-key" class="form-label">API PASSWORD</label> <label for="promosms-key" class="form-label">{{ $("promosmsPassword") }}</label>
<HiddenInput id="promosms-key" v-model="$parent.notification.promosmsPassword" :required="true" autocomplete="one-time-code"></HiddenInput> <HiddenInput id="promosms-key" v-model="$parent.notification.promosmsPassword" :required="true" autocomplete="one-time-code"></HiddenInput>
</div> </div>
<div class="mb-3"> <div class="mb-3">

View file

@ -18,28 +18,29 @@
</select> </select>
<label for="pushover-sound" class="form-label">{{ $t("Notification Sound") }}</label> <label for="pushover-sound" class="form-label">{{ $t("Notification Sound") }}</label>
<select id="pushover-sound" v-model="$parent.notification.pushoversounds" class="form-select"> <select id="pushover-sound" v-model="$parent.notification.pushoversounds" class="form-select">
<option>pushover</option> <option value="pushover">{{ $t("pushoversounds pushover") }}</option>
<option>bike</option> <option value="bike">{{ $t("pushoversounds bike") }}</option>
<option>bugle</option> <option value="bugle">{{ $t("pushoversounds bugle") }}</option>
<option>cashregister</option> <option value="cashregister">{{ $t("pushoversounds cashregister") }}</option>
<option>classical</option> <option value="classical">{{ $t("pushoversounds classical") }}</option>
<option>cosmic</option> <option value="cosmic">{{ $t("pushoversounds cosmic") }}</option>
<option>falling</option> <option value="falling">{{ $t("pushoversounds falling") }}</option>
<option>gamelan</option> <option value="gamelan">{{ $t("pushoversounds gamelan") }}</option>
<option>incoming</option> <option value="incoming">{{ $t("pushoversounds incoming") }}</option>
<option>intermission</option> <option value="intermission">{{ $t("pushoversounds intermission") }}</option>
<option>mechanical</option> <option value="magic">{{ $t("pushoversounds magic") }}</option>
<option>pianobar</option> <option value="mechanical">{{ $t("pushoversounds mechanical") }}</option>
<option>siren</option> <option value="pianobar">{{ $t("pushoversounds pianobar") }}</option>
<option>spacealarm</option> <option value="siren">{{ $t("pushoversounds siren") }}</option>
<option>tugboat</option> <option value="spacealarm">{{ $t("pushoversounds spacealarm") }}</option>
<option>alien</option> <option value="tugboat">{{ $t("pushoversounds tugboat") }}</option>
<option>climb</option> <option value="alien">{{ $t("pushoversounds alien") }}</option>
<option>persistent</option> <option value="climb">{{ $t("pushoversounds climb") }}</option>
<option>echo</option> <option value="persistent">{{ $t("pushoversounds persistent") }}</option>
<option>updown</option> <option value="echo">{{ $t("pushoversounds echo") }}</option>
<option>vibrate</option> <option value="updown">{{ $t("pushoversounds updown") }}</option>
<option>none</option> <option value="vibrate">{{ $t("pushoversounds vibrate") }}</option>
<option value="none">{{ $t("pushoversounds none") }}</option>
</select> </select>
<div class="form-text"> <div class="form-text">
<span style="color: red;"><sup>*</sup></span>{{ $t("Required") }} <span style="color: red;"><sup>*</sup></span>{{ $t("Required") }}

View file

@ -1,11 +1,11 @@
<template> <template>
<div class="mb-3"> <div class="mb-3">
<label for="pushy-app-token" class="form-label">API_KEY</label> <label for="pushy-app-token" class="form-label">{{ $t("pushyAPIKey") }}</label>
<HiddenInput id="pushy-app-token" v-model="$parent.notification.pushyAPIKey" :required="true" autocomplete="one-time-code"></HiddenInput> <HiddenInput id="pushy-app-token" v-model="$parent.notification.pushyAPIKey" :required="true" autocomplete="one-time-code"></HiddenInput>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label for="pushy-user-key" class="form-label">USER_TOKEN</label> <label for="pushy-user-key" class="form-label">{{ $t("pushyToken") }}</label>
<div class="input-group mb-3"> <div class="input-group mb-3">
<HiddenInput id="pushy-user-key" v-model="$parent.notification.pushyToken" :required="true" autocomplete="one-time-code"></HiddenInput> <HiddenInput id="pushy-user-key" v-model="$parent.notification.pushyToken" :required="true" autocomplete="one-time-code"></HiddenInput>
</div> </div>

View file

@ -1,6 +1,6 @@
<template> <template>
<div class="mb-3"> <div class="mb-3">
<label for="push-api-key" class="form-label">API_KEY</label> <label for="push-api-key" class="form-label">{{ $t("API Key") }}</label>
<HiddenInput id="push-api-key" v-model="$parent.notification.pushAPIKey" :required="true" autocomplete="one-time-code"></HiddenInput> <HiddenInput id="push-api-key" v-model="$parent.notification.pushAPIKey" :required="true" autocomplete="one-time-code"></HiddenInput>
</div> </div>

View file

@ -9,11 +9,11 @@
<div class="mt-1"> <div class="mt-1">
<div class="form-check"> <div class="form-check">
<label><input v-model="settings.checkUpdate" type="checkbox" @change="saveSettings()" /> Show update if available</label> <label><input v-model="settings.checkUpdate" type="checkbox" @change="saveSettings()" /> {{ $t("Show update if available") }}</label>
</div> </div>
<div class="form-check"> <div class="form-check">
<label><input v-model="settings.checkBeta" type="checkbox" :disabled="!settings.checkUpdate" @change="saveSettings()" /> Also check beta release</label> <label><input v-model="settings.checkBeta" type="checkbox" :disabled="!settings.checkUpdate" @change="saveSettings()" /> {{ $t("Also check beta release") }}</label>
</div> </div>
</div> </div>
</div> </div>

View file

@ -487,4 +487,55 @@ export default {
"Domain Names": "Domain Names", "Domain Names": "Domain Names",
signedInDisp: "Signed in as {0}", signedInDisp: "Signed in as {0}",
signedInDispDisabled: "Auth Disabled.", signedInDispDisabled: "Auth Disabled.",
"Certificate Expiry Notification": "Certificate Expiry Notification",
"API Username": "API Username",
"API Key": "API Key",
"Recipient Number": "Recipient Number",
"From Name/Number": "From Name/Number",
"Leave blank to use a shared sender number.": "Leave blank to use a shared sender number.",
"Octopush API Version": "Octopush API Version",
"Legacy Octopush-DM": "Legacy Octopush-DM",
"endpoint": "endpoint",
octopushAPIKey: "\"API key\" from HTTP API credentials in control panel",
octopushLogin: "\"Login\" from HTTP API credentials in control panel",
promosmsLogin: "API Login Name",
promosmsPassword: "API Password",
"pushoversounds pushover": "Pushover (default)",
"pushoversounds bike": "Bike",
"pushoversounds bugle": "Bugle",
"pushoversounds cashregister": "Cash Register",
"pushoversounds classical": "Classical",
"pushoversounds cosmic": "Cosmic",
"pushoversounds falling": "Falling",
"pushoversounds gamelan": "Gamelan",
"pushoversounds incoming": "Incoming",
"pushoversounds intermission": "Intermission",
"pushoversounds magic": "Magic",
"pushoversounds mechanical": "Mechanical",
"pushoversounds pianobar": "Piano Bar",
"pushoversounds siren": "Siren",
"pushoversounds spacealarm": "Space Alarm",
"pushoversounds tugboat": "Tug Boat",
"pushoversounds alien": "Alien Alarm (long)",
"pushoversounds climb": "Climb (long)",
"pushoversounds persistent": "Persistent (long)",
"pushoversounds echo": "Pushover Echo (long)",
"pushoversounds updown": "Up Down (long)",
"pushoversounds vibrate": "Vibrate Only",
"pushoversounds none": "None (silent)",
pushyAPIKey: "Secret API Key",
pushyToken: "Device token",
"Show update if available": "Show update if available",
"Also check beta release": "Also check beta release",
"Using a Reverse Proxy?": "Using a Reverse Proxy?",
"Check how to config it for WebSocket": "Check how to config it for WebSocket",
"Steam Game Server": "Steam Game Server",
"Most likely causes:": "Most likely causes:",
"The resource is no longer available.": "The resource is no longer available.",
"There might be a typing error in the address.": "There might be a typing error in the address.",
"What you can try:": "What you can try:",
"Retype the address.": "Retype the address.",
"Go back to the previous page.": "Go back to the previous page.",
"Coming Soon": "Coming Soon",
wayToGetClickSendSMSToken: "You can get API Username and API Key from {0} .",
}; };

View file

@ -88,7 +88,7 @@ export default {
Dark: "黑暗", Dark: "黑暗",
Auto: "自动", Auto: "自动",
"Theme - Heartbeat Bar": "主题 - 心跳栏", "Theme - Heartbeat Bar": "主题 - 心跳栏",
Normal: "正常", // 此处还供 Gorush 的通知优先级功能使用,不应翻译为“正常显示” Normal: "正常",
Bottom: "靠下", Bottom: "靠下",
None: "不显示", None: "不显示",
Timezone: "时区", Timezone: "时区",
@ -398,11 +398,9 @@ export default {
Invalid: "无效", Invalid: "无效",
AccessKeyId: "AccessKey ID", AccessKeyId: "AccessKey ID",
SecretAccessKey: "AccessKey Secret", SecretAccessKey: "AccessKey Secret",
/* 以下为阿里云短信服务 API Dysms#SendSms 的参数 */
PhoneNumbers: "PhoneNumbers", PhoneNumbers: "PhoneNumbers",
TemplateCode: "TemplateCode", TemplateCode: "TemplateCode",
SignName: "SignName", SignName: "SignName",
/* 以上为阿里云短信服务 API Dysms#SendSms 的参数 */
"Bark Endpoint": "Bark 接入点", "Bark Endpoint": "Bark 接入点",
"Device Token": "Apple Device Token", "Device Token": "Apple Device Token",
Platform: "平台", Platform: "平台",
@ -441,7 +439,7 @@ export default {
"No Proxy": "无代理", "No Proxy": "无代理",
"HTTP Basic Auth": "HTTP 基础身份验证", "HTTP Basic Auth": "HTTP 基础身份验证",
"New Status Page": "新的状态页", "New Status Page": "新的状态页",
"Page Not Found": "状态页未找到", "Page Not Found": "未找到该页面",
"Reverse Proxy": "反向代理", "Reverse Proxy": "反向代理",
"Subject:": "颁发给:", "Subject:": "颁发给:",
"Valid To:": "有效期至:", "Valid To:": "有效期至:",
@ -469,4 +467,57 @@ export default {
"Footer Text": "底部自定义文本", "Footer Text": "底部自定义文本",
"Show Powered By": "显示 Powered By", "Show Powered By": "显示 Powered By",
"Domain Names": "域名", "Domain Names": "域名",
"Certificate Expiry Notification": "证书到期时通知",
"API Username": "API 凭证 Username",
"API Key": "API 凭证 Key",
"Recipient Number": "收件人手机号码",
"From Name/Number": "发件人名称/手机号码",
"Leave blank to use a shared sender number.": "留空以使用平台共享的发件人手机号码",
"Octopush API Version": "Octopush API 版本",
"Legacy Octopush-DM": "旧版本 Octopush-DM",
endpoint: "接入点",
octopushAPIKey: "控制台 HTTP API credentials 里的 \"API key\"",
octopushLogin: "控制台 HTTP API credentials 里的 \"Login\"",
promosmsLogin: "API 登录名",
promosmsPassword: "API 密码",
"pushoversounds pushover": "Pushover默认",
"pushoversounds bike": "Bike",
"pushoversounds bugle": "Bugle",
"pushoversounds cashregister": "Cash Register",
"pushoversounds classical": "Classical",
"pushoversounds cosmic": "Cosmic",
"pushoversounds falling": "Falling",
"pushoversounds gamelan": "Gamelan",
"pushoversounds incoming": "Incoming",
"pushoversounds intermission": "Intermission",
"pushoversounds magic": "Magic",
"pushoversounds mechanical": "Mechanical",
"pushoversounds pianobar": "Piano Bar",
"pushoversounds siren": "Siren",
"pushoversounds spacealarm": "Space Alarm",
"pushoversounds tugboat": "Tug Boat",
"pushoversounds alien": "Alien Alarm长铃声",
"pushoversounds climb": "Climb长铃声",
"pushoversounds persistent": "Persistent长铃声",
"pushoversounds echo": "Pushover Echo长铃声",
"pushoversounds updown": "Up Down长铃声",
"pushoversounds vibrate": "仅震动",
"pushoversounds none": "无(禁音)",
pushyAPIKey: "API 密钥",
pushyToken: "设备 Token",
"Show update if available": "有更新时通知",
"Also check beta release": "一并检查 Beta 版更新",
"Using a Reverse Proxy?": "正在使用反向代理?",
"Check how to config it for WebSocket": "查看如何将反向代理与 WebSocket 一起使用",
"Steam Game Server": "Steam 游戏服务器",
"Most likely causes:": "最可能的原因:",
"The resource is no longer available.": "您所请求的资源已不再可用;",
"There might be a typing error in the address.": "您输入的地址可能有误。",
"What you can try:": "您可以尝试以下操作:",
"Retype the address.": "重新输入地址;",
"Go back to the previous page.": "返回到上一页面。",
"Coming Soon": "即将推出",
wayToGetClickSendSMSToken: "您可以从 {0} 获取 API 凭证 Username 和 凭证 Key。",
signedInDisp: "当前用户: {0}",
signedInDispDisabled: "已禁用身份验证",
}; };

View file

@ -4,7 +4,7 @@
<div class="container-fluid"> <div class="container-fluid">
{{ $root.connectionErrorMsg }} {{ $root.connectionErrorMsg }}
<div v-if="$root.showReverseProxyGuide"> <div v-if="$root.showReverseProxyGuide">
Using a Reverse Proxy? <a href="https://github.com/louislam/uptime-kuma/wiki/Reverse-Proxy" target="_blank">Check how to config it for WebSocket</a> {{ $t("Using a Reverse Proxy?") }} <a href="https://github.com/louislam/uptime-kuma/wiki/Reverse-Proxy" target="_blank">{{ $t("Check how to config it for WebSocket") }}</a>
</div> </div>
</div> </div>
</div> </div>

View file

@ -30,7 +30,7 @@
Push Push
</option> </option>
<option value="steam"> <option value="steam">
Steam Game Server {{ $t("Steam Game Server") }}
</option> </option>
<option value="mqtt"> <option value="mqtt">
MQTT MQTT

View file

@ -22,16 +22,16 @@
</div> </div>
<div class="guide"> <div class="guide">
Most likely causes: {{ $t("Most likely causes:") }}
<ul> <ul>
<li>The resource is no longer available.</li> <li>{{ $t("The resource is no longer available.") }}</li>
<li>There might be a typing error in the address.</li> <li>{{ $t("There might be a typing error in the address.") }}</li>
</ul> </ul>
What you can try:<br /> {{ $t("What you can try:") }}<br />
<ul> <ul>
<li>Retype the address.</li> <li>{{ $t("Retype the address.") }}</li>
<li><a href="#" class="go-back" @click="goBack()">Go back to the previous page.</a></li> <li><a href="#" class="go-back" @click="goBack()">{{ $t("Go back to the previous page.") }}</a></li>
</ul> </ul>
</div> </div>
</div> </div>

View file

@ -45,7 +45,7 @@
</div> </div>
<div v-if="false" class="my-3"> <div v-if="false" class="my-3">
<label for="password" class="form-label">{{ $t("Password") }} <sup>Coming Soon</sup></label> <label for="password" class="form-label">{{ $t("Password") }} <sup>{{ $t("Coming Soon") }}</sup></label>
<input id="password" v-model="config.password" disabled type="password" autocomplete="new-password" class="form-control"> <input id="password" v-model="config.password" disabled type="password" autocomplete="new-password" class="form-control">
</div> </div>
@ -733,7 +733,7 @@ export default {
}, },
statusPageLogoLoaded(eventPayload) { statusPageLogoLoaded(eventPayload) {
// Remark: may not work in dev, due to cros // Remark: may not work in dev, due to CORS
favicon.image(eventPayload.target); favicon.image(eventPayload.target);
}, },

View file

@ -1,5 +1,9 @@
const { genSecret } = require("../src/util"); const { genSecret, DOWN } = require("../src/util");
const utilServerRewire = require("../server/util-server"); const utilServerRewire = require("../server/util-server");
const Discord = require("../server/notification-providers/discord");
const axios = require("axios");
jest.mock("axios");
describe("Test parseCertificateInfo", () => { describe("Test parseCertificateInfo", () => {
it("should handle undefined", async () => { it("should handle undefined", async () => {
@ -164,6 +168,68 @@ describe("Test reset-password", () => {
}, 120000); }, 120000);
}); });
describe("Test Discord Notification Provider", () => {
const sendNotification = async (hostname, port, type) => {
const discordProvider = new Discord();
axios.post.mockResolvedValue({});
await discordProvider.send(
{
discordUsername: "Uptime Kuma",
discordWebhookUrl: "https://discord.com",
},
"test message",
{
type,
hostname,
port,
},
{
status: DOWN,
}
);
};
it("should send hostname for dns monitors", async () => {
const hostname = "discord.com";
await sendNotification(hostname, null, "dns");
expect(axios.post.mock.lastCall[1].embeds[0].fields[1].value).toBe(
hostname
);
});
it("should send hostname for ping monitors", async () => {
const hostname = "discord.com";
await sendNotification(hostname, null, "ping");
expect(axios.post.mock.lastCall[1].embeds[0].fields[1].value).toBe(
hostname
);
});
it("should send hostname for port monitors", async () => {
const hostname = "discord.com";
const port = 1337;
await sendNotification(hostname, port, "port");
expect(axios.post.mock.lastCall[1].embeds[0].fields[1].value).toBe(
`${hostname}:${port}`
);
});
it("should send hostname for steam monitors", async () => {
const hostname = "discord.com";
const port = 1337;
await sendNotification(hostname, port, "steam");
expect(axios.post.mock.lastCall[1].embeds[0].fields[1].value).toBe(
`${hostname}:${port}`
);
});
});
describe("The function filterAndJoin", () => { describe("The function filterAndJoin", () => {
it("should join and array of strings to one string", () => { it("should join and array of strings to one string", () => {
const result = utilServerRewire.filterAndJoin(["one", "two", "three"]); const result = utilServerRewire.filterAndJoin(["one", "two", "three"]);
@ -185,4 +251,3 @@ describe("The function filterAndJoin", () => {
expect(result).toBe(""); expect(result).toBe("");
}); });
}); });