Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FEAT: Added ping as status check #1811

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,21 @@ RUN \

# Create and set the working directory
WORKDIR /app

# Install python/pip
#ENV PYTHONUNBUFFERED=1
#RUN apk add --update --no-cache python3 && ln -sf python3 /usr/bin/python
# Install app dependencies
#RUN apk add cairo
RUN apk add --no-cache python3 g++ make build-base cairo-dev jpeg-dev pango-dev \
musl-dev \
giflib-dev \
pixman-dev \
pangomm-dev \
libjpeg-turbo-dev \
freetype-dev \
&& npm install canvas@3.1.0


COPY package.json yarn.lock ./
RUN yarn install --ignore-engines --immutable --no-cache --network-timeout 300000 --network-concurrency 1

Expand All @@ -38,6 +51,15 @@ WORKDIR ${DIRECTORY}
# Update tzdata for setting timezone
RUN apk add --no-cache tzdata

RUN apk add --no-cache python3 g++ make build-base cairo-dev jpeg-dev pango-dev \
musl-dev \
giflib-dev \
pixman-dev \
pangomm-dev \
libjpeg-turbo-dev \
freetype-dev \
&& npm install canvas@3.1.0

# Copy built application from build phase
COPY --from=BUILD_IMAGE /app ./

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"@sentry/tracing": "^7.102.1",
"@sentry/vue": "^7.102.1",
"ajv": "^8.10.0",
"canvas": "^3.1.0",
"axios": "^1.6.0",
"connect-history-api-fallback": "^1.6.0",
"crypto-js": "^4.2.0",
Expand Down
170 changes: 117 additions & 53 deletions services/status-check.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,81 +5,145 @@
*/
const axios = require('axios').default;
const https = require('https');
const { Image } = require('canvas');

/* Determines if successful from the HTTP response code */
const getResponseType = (code, validCodes) => {
if (validCodes && String(validCodes).includes(String(code))) return true;
if (Number.isNaN(code)) return false;
const numericCode = parseInt(code, 10);
return (numericCode >= 200 && numericCode <= 302);


// Provided pingFunction function (as is)
function pingFunction(ip, callback) {
if(!this.inUse) {
this.inUse = true;
this.callback = callback;
this.ip = ip;

var _that = this;

this.img = new Image();
this.img.onload = function() {_that.good();};
this.img.onerror = function() {_that.good();}; // onerror also treated as good in original code
this.start = new Date().getTime();
this.img.src = "http://" + ip + "/favicon.ico?" + this.start; // Added favicon.ico and timestamp to prevent caching, and ensure HTTP request
this.timer = setTimeout(function() { _that.bad();}, 1500);
}
}

pingFunction.prototype.good = function() {
if(this.timer) { clearTimeout(this.timer); }
var timeTaken = new Date().getTime() - this.start;
this.callback('good', timeTaken, this.ip);
this.inUse = false;
};

pingFunction.prototype.bad = function() {
if(this.timer) { clearTimeout(this.timer); }
this.callback('bad', false, this.ip);
this.inUse = false;
};

/* Makes human-readable response text for successful check */
const makeMessageText = (data) => `${data.successStatus ? '✅' : '⚠️'} `
+ `${data.serverName || 'Server'} responded with `
+ `${data.statusCode} - ${data.statusText}. `
+ `\n⏱️Took ${data.timeTaken} ms`;

/* Makes human-readable response text for failed check */
const makeErrorMessage = (data) => `❌ Service Unavailable: ${data.hostname || 'Server'} `
+ `resulted in ${data.code || 'a fatal error'} ${data.errno ? `(${data.errno})` : ''}`;
const getResponseType = (statusCode, validCodes) => {
if (!validCodes) {
return statusCode >= 200 && statusCode < 300;
}
return String(validCodes).split(',').map(code => parseInt(code.trim(), 10)).includes(statusCode);
};

const makeMessageText = (results) => {
return `Status: ${results.successStatus ? 'Success' : 'Failure'} - Code: ${results.statusCode} - Text: ${results.statusText || 'N/A'} - Server: ${results.serverName || 'N/A'} - Time: ${results.timeTaken}ms`;
};

const makeErrorMessage = (error) => {
return `Request failed: ${error.message}`;
};
const makeErrorMessage2 = (response) => {
return `Request failed: Status ${response.status} - ${response.statusText}`;
};

const makeErrorMessage2 = (data) => '❌ Service Error - '
+ `${data.status} - ${data.statusText}`;

/* Kicks of a HTTP request, then formats and renders results */
const makeRequest = (url, options, render) => {
const {
headers, enableInsecure, acceptCodes, maxRedirects,
} = options;
const validCodes = acceptCodes && acceptCodes !== 'null' ? acceptCodes : null;
const startTime = new Date();
const requestMaker = axios.create({
httpsAgent: new https.Agent({
rejectUnauthorized: !enableInsecure,
}),
});
requestMaker.request({
url,
headers,
maxRedirects,
})
.then((response) => {
const statusCode = response.status;
const { statusText } = response;
const successStatus = getResponseType(statusCode, validCodes);
const serverName = response.request.socket.servername;
const timeTaken = (new Date() - startTime);
const results = {
statusCode, statusText, serverName, successStatus, timeTaken,
};
results.message = makeMessageText(results);
return results;


if (url.startsWith('ping://')) {
const ipAddress = url.substring(7); // Remove "ping" prefix
const pinger = new pingFunction(ipAddress, (status, timeTaken, targetIp) => {
let results = {};
if (status === 'good') {
results = {
successStatus: true,
message: `Ping Success - Target: ${targetIp} - Time: ${timeTaken}ms`,
timeTaken: timeTaken,
targetIp: targetIp,
statusCode: 'N/A', // or a specific code for ping success?
statusText: 'Ping Success'
};
} else {
results = {
successStatus: false,
message: `Ping Failed - Target: ${targetIp}`,
timeTaken: timeTaken || 'N/A',
targetIp: targetIp,
statusCode: 'N/A', // or a specific code for ping failure?
statusText: 'Ping Failed'
};
}
render(JSON.stringify(results));
});
// Initiate ping is already done inside pingFunction constructor. No need to call any extra function here, as it is self-starting.

} else {
const requestMaker = axios.create({
httpsAgent: new https.Agent({
rejectUnauthorized: !enableInsecure,
}),
});
requestMaker.request({
url,
headers,
maxRedirects,
})
.catch((error) => {
const response = error ? (error.response || {}) : {};
const returnCode = response.status || response.code;
if (validCodes && String(validCodes).includes(returnCode)) { // Success overridden by user
.then((response) => {
const statusCode = response.status;
const { statusText } = response;
const successStatus = getResponseType(statusCode, validCodes);
const serverName = response.request.socket.servername;
const timeTaken = (new Date() - startTime);
const results = {
successStatus: getResponseType(returnCode, validCodes),
statusCode: returnCode,
statusText: response.statusText,
timeTaken: (new Date() - startTime),
statusCode, statusText, serverName, successStatus, timeTaken,
};
results.message = makeMessageText(results);
return results;
} else { // Request failed
return {
successStatus: false,
message: error.response ? makeErrorMessage2(error.response) : makeErrorMessage(error),
};
}
}).then((results) => {
})
.catch((error) => {
const response = error ? (error.response || {}) : {};
const returnCode = response.status || response.code;
if (validCodes && String(validCodes).includes(returnCode)) { // Success overridden by user
const results = {
successStatus: getResponseType(returnCode, validCodes),
statusCode: returnCode,
statusText: response.statusText,
timeTaken: (new Date() - startTime),
};
results.message = makeMessageText(results);
return results;
} else { // Request failed
return {
successStatus: false,
message: error.response ? makeErrorMessage2(error.response) : makeErrorMessage(error),
};
}
}).then((results) => {
// Request completed (either successfully, or failed) - render results
render(JSON.stringify(results));
});
}
};


const decodeHeaders = (maybeHeaders) => {
if (!maybeHeaders) return {};
const decodedHeaders = decodeURIComponent(maybeHeaders);
Expand Down
2 changes: 1 addition & 1 deletion src/components/Widgets/GlNetworkInterfaces.vue
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export default {
networks.push({
name: network.interface_name,
speed: network.speed,
online: network.speed ? 'up' : 'down', //v3 to v4 is_up no longer seems to be a default response field
online: network.speed ? 'up' : 'down', // v3 to v4 is_up no longer seems to be a default response field
currentDownload: network.bytes_recv,
currentUpload: network.bytes_sent,
totalDownload: network.bytes_recv_gauge,
Expand Down