DEVOPS-000 add manual job

This commit is contained in:
Maksim Kachynski 2025-06-10 11:59:51 +03:00
parent c46772dafc
commit dbc79f8d8d
No known key found for this signature in database
8 changed files with 119 additions and 6 deletions

View file

@ -636,7 +636,17 @@ class Monitor extends BeanModel {
bean.duration = beatInterval; bean.duration = beatInterval;
throw new Error("No heartbeat in the time window"); throw new Error("No heartbeat in the time window");
} }
} else if (this.type === "manual") {
const lastHeartbeat = await Monitor.getPreviousHeartbeat(this.id);
if (lastHeartbeat) {
bean.status = lastHeartbeat.status;
bean.msg = lastHeartbeat.msg || "Manual monitoring";
} else {
bean.status = PENDING;
bean.msg = "Manual monitoring - No previous status";
}
bean.time = new Date().getTime();
retries = 0;
} else if (this.type === "steam") { } else if (this.type === "steam") {
const steamApiUrl = "https://api.steampowered.com/IGameServersService/GetServerList/v1/"; const steamApiUrl = "https://api.steampowered.com/IGameServersService/GetServerList/v1/";
const steamAPIKey = await setting("steamAPIKey"); const steamAPIKey = await setting("steamAPIKey");

View file

@ -0,0 +1,26 @@
const { MonitorType } = require("./monitor-type");
class ManualMonitorType extends MonitorType {
name = "Manual";
type = "manual";
description = "Manual monitoring";
supportsConditions = false;
conditionVariables = [];
/**
* Checks the status of the monitor manually
* @param {object} monitor - Monitor object
* @param {object} heartbeat - Object to write the status of the check
* @param {object} server - Server object
* @returns {Promise<void>}
*/
async check(monitor, heartbeat, server) {
heartbeat.status = monitor.status;
heartbeat.msg = monitor.msg || "Manual monitoring";
heartbeat.time = new Date().getTime();
}
}
module.exports = {
ManualMonitorType
};

View file

@ -1542,6 +1542,34 @@ let needSetup = false;
} }
}); });
socket.on("addHeartbeat", async (heartbeat, callback) => {
try {
checkLogin(socket);
let bean = R.dispense("heartbeat");
bean.monitor_id = heartbeat.monitorID;
bean.status = heartbeat.status;
bean.msg = heartbeat.msg;
bean.time = heartbeat.time;
bean.ping = heartbeat.ping;
bean.important = true;
await R.store(bean);
io.to(socket.userID).emit("heartbeat", bean.toJSON());
callback({
ok: true,
});
} catch (e) {
callback({
ok: false,
msg: e.message,
});
}
});
socket.on("clearStatistics", async (callback) => { socket.on("clearStatistics", async (callback) => {
try { try {
checkLogin(socket); checkLogin(socket);

View file

@ -543,6 +543,10 @@ class UptimeCalculator {
* @throws {Error} Invalid status * @throws {Error} Invalid status
*/ */
flatStatus(status) { flatStatus(status) {
if (typeof status !== "number") {
throw new Error("Invalid status: status must be a number");
}
switch (status) { switch (status) {
case UP: case UP:
case MAINTENANCE: case MAINTENANCE:
@ -550,8 +554,9 @@ class UptimeCalculator {
case DOWN: case DOWN:
case PENDING: case PENDING:
return DOWN; return DOWN;
default:
throw new Error("Invalid status: " + status);
} }
throw new Error("Invalid status");
} }
/** /**

View file

@ -118,6 +118,7 @@ class UptimeKumaServer {
UptimeKumaServer.monitorTypeList["snmp"] = new SNMPMonitorType(); UptimeKumaServer.monitorTypeList["snmp"] = new SNMPMonitorType();
UptimeKumaServer.monitorTypeList["mongodb"] = new MongodbMonitorType(); UptimeKumaServer.monitorTypeList["mongodb"] = new MongodbMonitorType();
UptimeKumaServer.monitorTypeList["rabbitmq"] = new RabbitMqMonitorType(); UptimeKumaServer.monitorTypeList["rabbitmq"] = new RabbitMqMonitorType();
UptimeKumaServer.monitorTypeList["manual"] = new ManualMonitorType();
// Allow all CORS origins (polling) in development // Allow all CORS origins (polling) in development
let cors = undefined; let cors = undefined;
@ -558,4 +559,5 @@ const { GroupMonitorType } = require("./monitor-types/group");
const { SNMPMonitorType } = require("./monitor-types/snmp"); const { SNMPMonitorType } = require("./monitor-types/snmp");
const { MongodbMonitorType } = require("./monitor-types/mongodb"); const { MongodbMonitorType } = require("./monitor-types/mongodb");
const { RabbitMqMonitorType } = require("./monitor-types/rabbitmq"); const { RabbitMqMonitorType } = require("./monitor-types/rabbitmq");
const { ManualMonitorType } = require("./monitor-types/manual");
const Monitor = require("./model/monitor"); const Monitor = require("./model/monitor");

View file

@ -7,7 +7,7 @@ export default {
props: { props: {
/** Value of date time */ /** Value of date time */
value: { value: {
type: String, type: [ String, Number ],
default: null, default: null,
}, },
/** Should only the date be displayed? */ /** Should only the date be displayed? */

View file

@ -1109,5 +1109,9 @@
"Phone numbers": "Phone numbers", "Phone numbers": "Phone numbers",
"Sender name": "Sender name", "Sender name": "Sender name",
"smsplanetNeedToApproveName": "Needs to be approved in the client panel", "smsplanetNeedToApproveName": "Needs to be approved in the client panel",
"Disable URL in Notification": "Disable URL in Notification" "Disable URL in Notification": "Disable URL in Notification",
"Manual": "Manual",
"Manual Status": "Manual Status",
"Manual status set to": "Manual status set to",
"Status updated": "Status updated"
} }

View file

@ -55,6 +55,9 @@
<option value="push"> <option value="push">
Push Push
</option> </option>
<option value="manual">
{{ $t("Manual") }}
</option>
</optgroup> </optgroup>
<optgroup :label="$t('Specific Monitor Type')"> <optgroup :label="$t('Specific Monitor Type')">
@ -770,6 +773,21 @@
<div class="my-3"> <div class="my-3">
<tags-manager ref="tagsManager" :pre-selected-tags="monitor.tags"></tags-manager> <tags-manager ref="tagsManager" :pre-selected-tags="monitor.tags"></tags-manager>
</div> </div>
<div v-if="monitor.type === 'manual'" class="mb-3">
<label class="form-label">{{ $t("Manual Status") }}</label>
<div class="btn-group w-100">
<button class="btn btn-success" @click="setManualStatus('up')">
<i class="fas fa-check"></i> {{ $t("Up") }}
</button>
<button class="btn btn-danger" @click="setManualStatus('down')">
<i class="fas fa-times"></i> {{ $t("Down") }}
</button>
<button class="btn btn-warning" @click="setManualStatus('maintenance')">
<i class="fas fa-tools"></i> {{ $t("Maintenance") }}
</button>
</div>
</div>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
@ -1181,7 +1199,10 @@ export default {
VueMultiselect, VueMultiselect,
EditMonitorConditions, EditMonitorConditions,
}, },
setup() {
const toast = useToast();
return { toast };
},
data() { data() {
return { return {
minInterval: MIN_INTERVAL_SECOND, minInterval: MIN_INTERVAL_SECOND,
@ -1208,7 +1229,6 @@ export default {
remoteBrowsersEnabled: false, remoteBrowsersEnabled: false,
}; };
}, },
computed: { computed: {
timeoutStep() { timeoutStep() {
return this.monitor.type === "ping" ? 1 : 0.1; return this.monitor.type === "ping" ? 1 : 0.1;
@ -2015,6 +2035,24 @@ message HealthCheckResponse {
} }
}, },
setManualStatus(status) {
let updatedMonitor = { ...this.monitor };
updatedMonitor.id = this.monitor.id;
this.$root.getSocket().emit("addHeartbeat", {
monitorID: this.monitor.id,
status: status === "up" ? 1 : status === "down" ? 0 : status === "maintenance" ? 3 : 2,
msg: status === "up" ? "Up" : status === "down" ? "Down" : "Maintenance",
time: new Date().getTime(),
ping: 0
}, (res) => {
if (res.ok) {
this.toast.success(this.$t("Success"));
} else {
this.toast.error(res.msg);
}
});
},
}, },
}; };
</script> </script>