mirror of
https://github.com/rajnandan1/kener.git
synced 2026-02-09 02:09:16 -06:00
fix: fix for #212, data interpolation introduced
This commit is contained in:
@@ -27,6 +27,10 @@ DNS Monitor is used to check the status of a DNS server. It can be configured to
|
||||
|
||||
Ping Monitor is used to check the status of a server. It can be configured to check the status of the server at different intervals.
|
||||
|
||||
### Data Interpolation
|
||||
|
||||
Kener interpolates the data for missing intervals. For example, suppose if you have a cron monitor of 5 minutes and it has run twice at 10AM and 10:05AM and the monitor is `DOWN` at 10AM. Kener will interpolate the data for 10:01AM, 10:02AM, 10:03AM and 10:04AM as `DOWN`. Similarly, if the monitor is UP at 10:05AM, Kener will interpolate the data for 10:06AM, 10:07AM, 10:08AM and 10:09AM as UP. Similarly for missing data points it will consider the previous data point. If there is no previous data point, it will consider it as `UP`.
|
||||
|
||||
---
|
||||
|
||||
## Triggers
|
||||
|
||||
@@ -29,4 +29,14 @@ Do not forget to add the base path if you are using a subpath. For example, if y
|
||||
|
||||
### Inline CSS
|
||||
|
||||
To add inline css go to Manage kener -> Theme -> Custom CSS and add your CSS there. Do not include `<style>` tags.
|
||||
To add inline css go to Manage kener -> Theme -> Custom CSS and add your CSS there.
|
||||
|
||||
```css
|
||||
.my-class {
|
||||
color: red;
|
||||
}
|
||||
```
|
||||
|
||||
<div class="note danger">
|
||||
Do not include <style> tags.
|
||||
</div>
|
||||
|
||||
@@ -90,3 +90,17 @@ Add font url
|
||||
### Font Family
|
||||
|
||||
Add font family name
|
||||
|
||||
### Custom CSS
|
||||
|
||||
You can add custom CSS to your status page. This will be added to the head of the page. You can add custom CSS to your status page. This will be added to the head of the page.
|
||||
|
||||
```css
|
||||
.my-class {
|
||||
color: red;
|
||||
}
|
||||
```
|
||||
|
||||
<div class="note danger">
|
||||
Do not include <style> tags.
|
||||
</div>
|
||||
|
||||
@@ -278,7 +278,7 @@
|
||||
.summaryColorClass}"
|
||||
>
|
||||
{l(lang, summaryTime(monitor.pageData.summaryStatus), {
|
||||
status: monitor.pageData.summaryStatus,
|
||||
status: l(lang, monitor.pageData.summaryStatus),
|
||||
duration: monitor.pageData.summaryDuration
|
||||
})}
|
||||
</div>
|
||||
@@ -304,7 +304,6 @@
|
||||
on:click={(e) => {
|
||||
dailyDataGetter(e, bar, incidents[ts]);
|
||||
}}
|
||||
style="transition: opacity {bar.ij * 2 + 100}ms ease-in;"
|
||||
href="#"
|
||||
class="oneline h-[34px] w-[6px]
|
||||
{bar.border ? 'opacity-100' : 'opacity-20'} pb-1"
|
||||
@@ -336,7 +335,7 @@
|
||||
)}
|
||||
-
|
||||
{l(lang, summaryTime(bar.summaryStatus), {
|
||||
status: bar.summaryStatus,
|
||||
status: l(lang, bar.summaryStatus),
|
||||
duration: bar.summaryDuration
|
||||
})}
|
||||
</div>
|
||||
|
||||
@@ -19,7 +19,7 @@ const fdn = function (start, locale) {
|
||||
};
|
||||
const fdm = function (duration, locale) {
|
||||
return formatDuration(duration, {
|
||||
format: ["hours", "minutes"],
|
||||
format: ["days", "hours", "minutes"],
|
||||
zero: false,
|
||||
delimiter: " ",
|
||||
locale: locales[locale]
|
||||
@@ -36,7 +36,6 @@ const l = function (sessionLangMap, key, args = {}) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Replace placeholders in the string using the args object
|
||||
if (obj && typeof obj === "string") {
|
||||
obj = obj.replace(/%\w+/g, (placeholder) => {
|
||||
|
||||
@@ -381,6 +381,29 @@ export const HashString = (str) => {
|
||||
return hash.digest("hex");
|
||||
};
|
||||
|
||||
export const InterpolateData = (data, start, anchorStatus, e) => {
|
||||
let finalData = [];
|
||||
let status = anchorStatus || "UP";
|
||||
let end = e || data[data.length - 1].timestamp;
|
||||
for (let i = start; i <= end; i += 60) {
|
||||
let nowData = data.find((d) => d.timestamp === i);
|
||||
if (!!nowData) {
|
||||
status = nowData.status;
|
||||
}
|
||||
|
||||
finalData.push({ timestamp: i, status: status });
|
||||
}
|
||||
return finalData;
|
||||
};
|
||||
|
||||
export const GetLastStatusBefore = async (monitor_tag, timestamp) => {
|
||||
let data = await db.getLastStatusBefore(monitor_tag, timestamp);
|
||||
if (data) {
|
||||
return data.status;
|
||||
}
|
||||
return "UP";
|
||||
};
|
||||
|
||||
export const GetDataGroupByDayAlternative = async (
|
||||
monitor_tag,
|
||||
start,
|
||||
@@ -394,7 +417,9 @@ export const GetDataGroupByDayAlternative = async (
|
||||
|
||||
const offsetSeconds = offsetMinutes * 60;
|
||||
|
||||
const rawData = await db.getDataGroupByDayAlternative(monitor_tag, start, end);
|
||||
let rawData = await db.getDataGroupByDayAlternative(monitor_tag, start, end);
|
||||
let anchorStatus = await GetLastStatusBefore(monitor_tag, start);
|
||||
rawData = InterpolateData(rawData, start, anchorStatus);
|
||||
const groupedData = rawData.reduce((acc, row) => {
|
||||
// Calculate day group considering timezone offset
|
||||
const dayGroup = Math.floor((row.timestamp + offsetSeconds) / 86400);
|
||||
|
||||
@@ -56,6 +56,16 @@ class DbImpl {
|
||||
.first();
|
||||
}
|
||||
|
||||
//get the last status before the timestamp given monitor_tag and start timestamp
|
||||
async getLastStatusBefore(monitor_tag, timestamp) {
|
||||
return await this.knex("monitoring_data")
|
||||
.where("monitor_tag", monitor_tag)
|
||||
.where("timestamp", "<", timestamp)
|
||||
.orderBy("timestamp", "desc")
|
||||
.limit(1)
|
||||
.first();
|
||||
}
|
||||
|
||||
async getDataGroupByDayAlternative(monitor_tag, start, end) {
|
||||
// Fetch all raw data
|
||||
//{ timestamp: 1732900380, status: 'UP', latency: 42 }
|
||||
|
||||
@@ -10,7 +10,11 @@ import {
|
||||
} from "$lib/server/tool.js";
|
||||
import { formatDuration, intervalToDuration } from "date-fns";
|
||||
import { fdm } from "$lib/i18n/client";
|
||||
import { GetDataGroupByDayAlternative } from "$lib/server/controllers/controller.js";
|
||||
import {
|
||||
GetDataGroupByDayAlternative,
|
||||
InterpolateData,
|
||||
GetLastStatusBefore
|
||||
} from "$lib/server/controllers/controller.js";
|
||||
|
||||
function getSummaryDuration(numOfMinute, selectedLang) {
|
||||
// Convert minutes to milliseconds and create duration object
|
||||
@@ -33,12 +37,12 @@ function getTimezoneOffset(timeZone) {
|
||||
return parseInt(hours) * 60 + parseInt(minutes);
|
||||
}
|
||||
|
||||
function returnStatusClass(val, up, c, barStyle) {
|
||||
function returnStatusClass(val, total, c, barStyle) {
|
||||
// return "api-degraded-10";
|
||||
if (barStyle === undefined || barStyle == "FULL" || up == 0) {
|
||||
if (barStyle === undefined || barStyle == "FULL") {
|
||||
return c;
|
||||
} else if (barStyle == "PARTIAL") {
|
||||
let totalHeight = 24 * 60;
|
||||
let totalHeight = total;
|
||||
let cl = `api-up`;
|
||||
if (val > 0 && val <= 0.1 * totalHeight) {
|
||||
cl = c + "-10";
|
||||
@@ -84,7 +88,6 @@ const FetchData = async function (site, monitor, localTz, selectedLang) {
|
||||
let offsetInMinutes = parseInt((GetDayStartTimestampUTC(now) - midnight) / 60);
|
||||
const _90Day = {};
|
||||
let latestTimestamp = 0;
|
||||
let ij = 0;
|
||||
for (let i = midnight90DaysAgo; i < midnightTomorrow; i += secondsInDay) {
|
||||
_90Day[i] = {
|
||||
UP: 0,
|
||||
@@ -96,10 +99,8 @@ const FetchData = async function (site, monitor, localTz, selectedLang) {
|
||||
summaryDuration: 0,
|
||||
summaryStatus: NO_DATA,
|
||||
message: NO_DATA,
|
||||
border: true,
|
||||
ij: ij
|
||||
border: true
|
||||
};
|
||||
ij++;
|
||||
latestTimestamp = i;
|
||||
}
|
||||
|
||||
@@ -117,6 +118,11 @@ const FetchData = async function (site, monitor, localTz, selectedLang) {
|
||||
let summaryStatus = "UP";
|
||||
|
||||
let summaryColorClass = "api-nodata";
|
||||
|
||||
let todayDataDb = await db.getMonitoringData(monitor.tag, midnight, midnight + secondsInDay);
|
||||
let anchorStatus = await GetLastStatusBefore(monitor.tag, midnight);
|
||||
todayDataDb = InterpolateData(todayDataDb, midnight, anchorStatus);
|
||||
|
||||
for (let i = 0; i < dbData.length; i++) {
|
||||
let dayData = dbData[i];
|
||||
let ts = dayData.timestamp;
|
||||
@@ -132,7 +138,7 @@ const FetchData = async function (site, monitor, localTz, selectedLang) {
|
||||
if (dayData.DEGRADED >= monitor.day_degraded_minimum_count) {
|
||||
cssClass = returnStatusClass(
|
||||
dayData.DEGRADED,
|
||||
dayData.UP,
|
||||
dayData.total,
|
||||
StatusObj.DEGRADED,
|
||||
site.barStyle
|
||||
);
|
||||
@@ -140,7 +146,13 @@ const FetchData = async function (site, monitor, localTz, selectedLang) {
|
||||
summaryStatus = "DEGRADED";
|
||||
}
|
||||
if (dayData.DOWN >= monitor.day_down_minimum_count) {
|
||||
cssClass = returnStatusClass(dayData.DOWN, dayData.UP, StatusObj.DOWN, site.barStyle);
|
||||
cssClass = returnStatusClass(
|
||||
dayData.DOWN,
|
||||
dayData.total,
|
||||
StatusObj.DOWN,
|
||||
site.barStyle
|
||||
);
|
||||
|
||||
summaryDuration = getSummaryDuration(dayData.DOWN, selectedLang);
|
||||
summaryStatus = "DOWN";
|
||||
}
|
||||
@@ -162,13 +174,8 @@ const FetchData = async function (site, monitor, localTz, selectedLang) {
|
||||
}
|
||||
// return _90Day;
|
||||
let uptime90Day = ParseUptime(uptime90DayNumerator, uptime90DayDenominator);
|
||||
if (site.summaryStyle === "CURRENT") {
|
||||
let todayDataDb = await db.getMonitoringData(
|
||||
monitor.tag,
|
||||
latestTimestamp,
|
||||
latestTimestamp + secondsInDay
|
||||
);
|
||||
|
||||
if (site.summaryStyle === "CURRENT") {
|
||||
summaryColorClass = "api-up";
|
||||
|
||||
let lastRow = todayDataDb[todayDataDb.length - 1];
|
||||
|
||||
@@ -86,7 +86,7 @@
|
||||
<img src="https://kener.ing/logo.png" class="h-8 w-8" alt="" />
|
||||
<span class="text-xl font-medium">Kener Documentation</span>
|
||||
<span class="me-2 rounded border px-2.5 py-0.5 text-xs font-medium">
|
||||
v3.0.0
|
||||
v3.0.2
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -8,7 +8,12 @@ import {
|
||||
ParseUptime
|
||||
} from "$lib/server/tool.js";
|
||||
import db from "$lib/server/db/db.js";
|
||||
import { GetAllSiteData, GetIncidentsByIDS } from "$lib/server/controllers/controller.js";
|
||||
import {
|
||||
GetAllSiteData,
|
||||
GetIncidentsByIDS,
|
||||
InterpolateData,
|
||||
GetLastStatusBefore
|
||||
} from "$lib/server/controllers/controller.js";
|
||||
|
||||
export async function POST({ request }) {
|
||||
const payload = await request.json();
|
||||
@@ -31,6 +36,8 @@ export async function POST({ request }) {
|
||||
}
|
||||
|
||||
let dayData = await db.getMonitoringData(monitor.tag, payload.startTs, end);
|
||||
let anchorStatus = await GetLastStatusBefore(monitor.tag, start);
|
||||
dayData = InterpolateData(dayData, payload.startTs, anchorStatus, end);
|
||||
let siteData = await GetAllSiteData();
|
||||
|
||||
let ups = 0;
|
||||
|
||||
@@ -8,18 +8,29 @@ import {
|
||||
ParseUptime
|
||||
} from "$lib/server/tool.js";
|
||||
import db from "$lib/server/db/db.js";
|
||||
import { InterpolateData, GetLastStatusBefore } from "$lib/server/controllers/controller.js";
|
||||
|
||||
export async function POST({ request }) {
|
||||
const payload = await request.json();
|
||||
const monitor = payload.monitor;
|
||||
const start = payload.startTs;
|
||||
let end = GetMinuteStartNowTimestampUTC();
|
||||
let aggregatedData = await db.getAggregatedMonitoringData(monitor.tag, start, end);
|
||||
let rawData = await db.getMonitoringData(monitor.tag, start, end);
|
||||
let anchorStatus = await GetLastStatusBefore(monitor.tag, start);
|
||||
rawData = InterpolateData(rawData, start, anchorStatus);
|
||||
|
||||
let aggregatedData = rawData.reduce(
|
||||
(acc, row) => {
|
||||
acc[row.status] = (acc[row.status] || 0) + 1;
|
||||
return acc;
|
||||
},
|
||||
{
|
||||
UP: 0,
|
||||
DOWN: 0,
|
||||
DEGRADED: 0
|
||||
}
|
||||
);
|
||||
//covert all keys to uppercase
|
||||
aggregatedData = Object.keys(aggregatedData).reduce((acc, key) => {
|
||||
acc[key.toUpperCase()] = aggregatedData[key];
|
||||
return acc;
|
||||
}, {});
|
||||
let ups = Number(aggregatedData.UP);
|
||||
let downs = Number(aggregatedData.DOWN);
|
||||
let degradeds = Number(aggregatedData.DEGRADED);
|
||||
|
||||
Reference in New Issue
Block a user