mirror of
https://github.com/rajnandan1/kener.git
synced 2026-03-13 14:29:57 -05:00
changes
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -12,3 +12,4 @@ config/site.yaml
|
||||
!.env.example
|
||||
vite.config.js.timestamp-*
|
||||
vite.config.ts.timestamp-*
|
||||
nodemon.json
|
||||
|
||||
202
package-lock.json
generated
202
package-lock.json
generated
@@ -12,7 +12,6 @@
|
||||
"@markdoc/markdoc": "^0.3.5",
|
||||
"axios": "^1.6.2",
|
||||
"bits-ui": "^0.9.8",
|
||||
"cheerio": "^1.0.0-rc.12",
|
||||
"clsx": "^2.0.0",
|
||||
"croner": "^7.0.5",
|
||||
"express": "^4.18.2",
|
||||
@@ -23,6 +22,7 @@
|
||||
"moment": "^2.29.4",
|
||||
"moment-timezone": "^0.5.43",
|
||||
"node-cache": "^5.1.2",
|
||||
"randomstring": "^1.3.0",
|
||||
"tailwind-merge": "^2.0.0",
|
||||
"tailwind-variants": "^0.1.18"
|
||||
},
|
||||
@@ -1084,11 +1084,6 @@
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
|
||||
},
|
||||
"node_modules/boolbase": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
|
||||
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||
@@ -1248,42 +1243,6 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/cheerio": {
|
||||
"version": "1.0.0-rc.12",
|
||||
"resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz",
|
||||
"integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==",
|
||||
"dependencies": {
|
||||
"cheerio-select": "^2.1.0",
|
||||
"dom-serializer": "^2.0.0",
|
||||
"domhandler": "^5.0.3",
|
||||
"domutils": "^3.0.1",
|
||||
"htmlparser2": "^8.0.1",
|
||||
"parse5": "^7.0.0",
|
||||
"parse5-htmlparser2-tree-adapter": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/cheeriojs/cheerio?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/cheerio-select": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz",
|
||||
"integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==",
|
||||
"dependencies": {
|
||||
"boolbase": "^1.0.0",
|
||||
"css-select": "^5.1.0",
|
||||
"css-what": "^6.1.0",
|
||||
"domelementtype": "^2.3.0",
|
||||
"domhandler": "^5.0.3",
|
||||
"domutils": "^3.0.1"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/fb55"
|
||||
}
|
||||
},
|
||||
"node_modules/chokidar": {
|
||||
"version": "3.5.3",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
|
||||
@@ -1507,21 +1466,6 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/css-select": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz",
|
||||
"integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==",
|
||||
"dependencies": {
|
||||
"boolbase": "^1.0.0",
|
||||
"css-what": "^6.1.0",
|
||||
"domhandler": "^5.0.2",
|
||||
"domutils": "^3.0.1",
|
||||
"nth-check": "^2.0.1"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/fb55"
|
||||
}
|
||||
},
|
||||
"node_modules/css-tree": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz",
|
||||
@@ -1534,17 +1478,6 @@
|
||||
"node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/css-what": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz",
|
||||
"integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==",
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/fb55"
|
||||
}
|
||||
},
|
||||
"node_modules/cssesc": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
|
||||
@@ -1669,57 +1602,6 @@
|
||||
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
|
||||
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="
|
||||
},
|
||||
"node_modules/dom-serializer": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
|
||||
"integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
|
||||
"dependencies": {
|
||||
"domelementtype": "^2.3.0",
|
||||
"domhandler": "^5.0.2",
|
||||
"entities": "^4.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/domelementtype": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
|
||||
"integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/fb55"
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/domhandler": {
|
||||
"version": "5.0.3",
|
||||
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
|
||||
"integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
|
||||
"dependencies": {
|
||||
"domelementtype": "^2.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/domhandler?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/domutils": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz",
|
||||
"integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==",
|
||||
"dependencies": {
|
||||
"dom-serializer": "^2.0.0",
|
||||
"domelementtype": "^2.3.0",
|
||||
"domhandler": "^5.0.3"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/domutils?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/ee-first": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||
@@ -1745,17 +1627,6 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/entities": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
|
||||
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
|
||||
"engines": {
|
||||
"node": ">=0.12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/es6-promise": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz",
|
||||
@@ -2193,24 +2064,6 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/htmlparser2": {
|
||||
"version": "8.0.2",
|
||||
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz",
|
||||
"integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==",
|
||||
"funding": [
|
||||
"https://github.com/fb55/htmlparser2?sponsor=1",
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/fb55"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"domelementtype": "^2.3.0",
|
||||
"domhandler": "^5.0.3",
|
||||
"domutils": "^3.0.1",
|
||||
"entities": "^4.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/http-errors": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
|
||||
@@ -2717,17 +2570,6 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/nth-check": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
|
||||
"integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
|
||||
"dependencies": {
|
||||
"boolbase": "^1.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/nth-check?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
@@ -2783,29 +2625,6 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/parse5": {
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz",
|
||||
"integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==",
|
||||
"dependencies": {
|
||||
"entities": "^4.4.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/inikulin/parse5?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/parse5-htmlparser2-tree-adapter": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz",
|
||||
"integrity": "sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==",
|
||||
"dependencies": {
|
||||
"domhandler": "^5.0.2",
|
||||
"parse5": "^7.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/inikulin/parse5?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/parseurl": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
||||
@@ -3096,6 +2915,25 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/randombytes": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.3.tgz",
|
||||
"integrity": "sha512-lDVjxQQFoCG1jcrP06LNo2lbWp4QTShEXnhActFBwYuHprllQV6VUpwreApsYqCgD+N1mHoqJ/BI/4eV4R2GYg=="
|
||||
},
|
||||
"node_modules/randomstring": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/randomstring/-/randomstring-1.3.0.tgz",
|
||||
"integrity": "sha512-gY7aQ4i1BgwZ8I1Op4YseITAyiDiajeZOPQUbIq9TPGPhUm5FX59izIaOpmKbME1nmnEiABf28d9K2VSii6BBg==",
|
||||
"dependencies": {
|
||||
"randombytes": "2.0.3"
|
||||
},
|
||||
"bin": {
|
||||
"randomstring": "bin/randomstring"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/range-parser": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
||||
|
||||
@@ -68,6 +68,7 @@
|
||||
"moment": "^2.29.4",
|
||||
"moment-timezone": "^0.5.43",
|
||||
"node-cache": "^5.1.2",
|
||||
"randomstring": "^1.3.0",
|
||||
"tailwind-merge": "^2.0.0",
|
||||
"tailwind-variants": "^0.1.18"
|
||||
}
|
||||
|
||||
@@ -3,20 +3,18 @@ import fs from "fs-extra";
|
||||
import { UP, DOWN, DEGRADED } from "./constants.js";
|
||||
import moment from "moment";
|
||||
|
||||
|
||||
function replaceAllOccurrences(originalString, searchString, replacement) {
|
||||
const regex = new RegExp(`\\${searchString}`, "g");
|
||||
const replacedString = originalString.replace(regex, replacement);
|
||||
return replacedString;
|
||||
}
|
||||
|
||||
const OneMinuteFetch = async (envSecrets, url, method, headers, body, timeout, cb, out) => {
|
||||
|
||||
let axiosHeaders = {};
|
||||
const Kener_folder = process.env.PUBLIC_KENER_FOLDER;
|
||||
const OneMinuteFetch = async (envSecrets, folderName, url, method, headers, body, timeout, cb, out, out90) => {
|
||||
let axiosHeaders = {};
|
||||
axiosHeaders["User-Agent"] = "Kener/0.0.1";
|
||||
axiosHeaders["Accept"] = "*/*";
|
||||
const start = Date.now();
|
||||
const startOfMinute = moment(start).startOf("minute").toISOString();
|
||||
const startOfMinute = moment(start).startOf("minute").toISOString();
|
||||
//replace all secrets
|
||||
for (let i = 0; i < envSecrets.length; i++) {
|
||||
const secret = envSecrets[i];
|
||||
@@ -28,13 +26,12 @@ const OneMinuteFetch = async (envSecrets, url, method, headers, body, timeout, c
|
||||
}
|
||||
if (!!headers) {
|
||||
headers = replaceAllOccurrences(headers, secret.find, secret.replace);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!!headers) {
|
||||
headers = JSON.parse(headers);
|
||||
axiosHeaders = { ...axiosHeaders, ...headers };
|
||||
}
|
||||
if (!!headers){
|
||||
headers = JSON.parse(headers);
|
||||
axiosHeaders = {...axiosHeaders, ...headers};
|
||||
}
|
||||
|
||||
|
||||
const options = {
|
||||
method: method,
|
||||
@@ -57,7 +54,7 @@ const OneMinuteFetch = async (envSecrets, url, method, headers, body, timeout, c
|
||||
statusCode = data.status;
|
||||
resp = data.data;
|
||||
} catch (err) {
|
||||
console.error("API call error: " + err.message);
|
||||
console.error("API call error: " + err.message);
|
||||
if (err.response !== undefined && err.response.status !== undefined) {
|
||||
statusCode = err.response.status;
|
||||
}
|
||||
@@ -83,25 +80,25 @@ const OneMinuteFetch = async (envSecrets, url, method, headers, body, timeout, c
|
||||
type: "error",
|
||||
};
|
||||
} else {
|
||||
evalResp.type = "realtime";
|
||||
}
|
||||
evalResp.type = "realtime";
|
||||
}
|
||||
|
||||
let toWrite = {
|
||||
let toWrite = {
|
||||
status: DOWN,
|
||||
latency: latency,
|
||||
type: "error",
|
||||
};
|
||||
if(evalResp.status !== undefined && evalResp.status !== null){
|
||||
toWrite.status = evalResp.status;
|
||||
}
|
||||
if(evalResp.latency !== undefined && evalResp.latency !== null){
|
||||
toWrite.latency = evalResp.latency;
|
||||
}
|
||||
if(evalResp.type !== undefined && evalResp.type !== null){
|
||||
toWrite.type = evalResp.type;
|
||||
}
|
||||
let objectToWrite = {};
|
||||
objectToWrite[startOfMinute] = toWrite;
|
||||
if (evalResp.status !== undefined && evalResp.status !== null) {
|
||||
toWrite.status = evalResp.status;
|
||||
}
|
||||
if (evalResp.latency !== undefined && evalResp.latency !== null) {
|
||||
toWrite.latency = evalResp.latency;
|
||||
}
|
||||
if (evalResp.type !== undefined && evalResp.type !== null) {
|
||||
toWrite.type = evalResp.type;
|
||||
}
|
||||
let objectToWrite = {};
|
||||
objectToWrite[startOfMinute] = toWrite;
|
||||
|
||||
let originalData = {};
|
||||
|
||||
@@ -114,26 +111,96 @@ const OneMinuteFetch = async (envSecrets, url, method, headers, body, timeout, c
|
||||
fs.writeFileSync(out, JSON.stringify({}));
|
||||
}
|
||||
|
||||
originalData[startOfMinute] = toWrite;
|
||||
originalData[startOfMinute] = toWrite;
|
||||
|
||||
//get data from webhook
|
||||
//read all files that end with .webhook
|
||||
let files = fs.readdirSync(Kener_folder);
|
||||
files = files.filter((file) => file.startsWith(folderName +".webhook"));
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const file = files[i];
|
||||
let webhookData = {};
|
||||
try {
|
||||
let fd = fs.readFileSync(Kener_folder + "/" + file, "utf8");
|
||||
webhookData = JSON.parse(fd);
|
||||
for (const timestampISO in webhookData) {
|
||||
originalData[timestampISO] = webhookData[timestampISO];
|
||||
}
|
||||
//delete the file
|
||||
fs.unlinkSync(Kener_folder + "/" + file);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//read originaldata, create a new file with 90days data
|
||||
let _90Day = {};
|
||||
let _90File = out90;
|
||||
try {
|
||||
let fd = fs.readFileSync(_90File, "utf8");
|
||||
_90Day = JSON.parse(fd);
|
||||
} catch (err) {
|
||||
fs.ensureFileSync(_90File);
|
||||
fs.writeFileSync(_90File, JSON.stringify({}));
|
||||
}
|
||||
let dayISO = moment(startOfMinute).startOf('day').toISOString();
|
||||
|
||||
//sort the keys
|
||||
let keys = Object.keys(originalData);
|
||||
keys.sort((a,b) => {
|
||||
return moment(a).isBefore(moment(b)) ? -1 : 1;
|
||||
});
|
||||
let sortedDay0 = {};
|
||||
keys.reverse() //reverse to keep 90days data
|
||||
.slice(0, 129600) //90days data
|
||||
.reverse() //reverse to keep 0day data
|
||||
.forEach((key) => {
|
||||
sortedDay0[key] = originalData[key];
|
||||
});
|
||||
try {
|
||||
fs.writeFileSync(out, JSON.stringify(sortedDay0, null, 2));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
if (_90Day[dayISO] === undefined) {
|
||||
_90Day[dayISO] = {
|
||||
timestamp: dayISO,
|
||||
UP: toWrite.status == UP ? 1 : 0,
|
||||
DEGRADED: toWrite.status == DEGRADED ? 1 : 0,
|
||||
DOWN: toWrite.status == DOWN ? 1 : 0,
|
||||
avgLatency: toWrite.latency,
|
||||
latency: toWrite.latency,
|
||||
};
|
||||
} else {
|
||||
let d = _90Day[dayISO];
|
||||
let up = toWrite.status == UP ? d.UP + 1 : d.UP;
|
||||
let degraded = toWrite.status == DEGRADED ? d.DEGRADED + 1 : d.DEGRADED;
|
||||
let down = toWrite.status == DOWN ? d.DOWN + 1 : d.DOWN;
|
||||
|
||||
_90Day[dayISO] = {
|
||||
timestamp: dayISO,
|
||||
UP: up,
|
||||
DEGRADED: degraded,
|
||||
DOWN: down,
|
||||
avgLatency: ((d.latency + toWrite.latency) / (d.UP + d.DEGRADED + d.DOWN + 1)).toFixed(0),
|
||||
latency: d.latency + toWrite.latency,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
fs.writeFileSync(_90File, JSON.stringify(_90Day, null, 2));
|
||||
|
||||
//from originaldata, delete all values older than today
|
||||
let today = moment().startOf('day').toISOString();
|
||||
let _0Day = {};
|
||||
for (const ts in originalData) {
|
||||
const element = originalData[ts];
|
||||
if (moment(ts).isAfter(moment(today))) {
|
||||
_0Day[ts] = element;
|
||||
}
|
||||
}
|
||||
|
||||
//sort the keys
|
||||
let keys = Object.keys(_0Day);
|
||||
keys.sort((a, b) => {
|
||||
return moment(a).isBefore(moment(b)) ? -1 : 1;
|
||||
});
|
||||
let sortedDay0 = {};
|
||||
keys.reverse() //reverse to keep 90days data
|
||||
.slice(0, 1440) //90days data
|
||||
.reverse() //reverse to keep 0day data
|
||||
.forEach((key) => {
|
||||
sortedDay0[key] = _0Day[key];
|
||||
});
|
||||
try {
|
||||
fs.writeFileSync(out, JSON.stringify(sortedDay0, null, 2));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
export { OneMinuteFetch };
|
||||
|
||||
@@ -117,9 +117,10 @@ const Startup = async () => {
|
||||
}
|
||||
if (monitor.timeout === undefined || monitor.timeout === null) {
|
||||
monitors[i].timeout = API_TIMEOUT;
|
||||
}
|
||||
}
|
||||
|
||||
monitors[i].path0Day = `${FOLDER}/${folderName}-day-json.json`;
|
||||
monitors[i].path90Day = `${FOLDER}/${folderName}.90day.json`;
|
||||
monitors[i].hasAPI = hasAPI;
|
||||
|
||||
//secrets can be in url/body/headers
|
||||
@@ -165,7 +166,7 @@ const Startup = async () => {
|
||||
continue;
|
||||
}
|
||||
console.log("Staring One Minute Cron for ", monitor.path0Day);
|
||||
await OneMinuteFetch(envSecrets, monitor.url, monitor.method, JSON.stringify(monitor.headers), monitor.body, monitor.timeout, monitor.eval, monitor.path0Day);
|
||||
await OneMinuteFetch(envSecrets, monitor.folderName, monitor.url, monitor.method, JSON.stringify(monitor.headers), monitor.body, monitor.timeout, monitor.eval, monitor.path0Day, monitor.path90Day);
|
||||
}
|
||||
|
||||
//trigger minute cron
|
||||
@@ -181,7 +182,7 @@ const Startup = async () => {
|
||||
}
|
||||
console.log("Staring " + cronExpession + " Cron for ", monitor.name);
|
||||
Cron(cronExpession, async () => {
|
||||
OneMinuteFetch(envSecrets, monitor.url, monitor.method, JSON.stringify(monitor.headers), monitor.body, monitor.timeout, monitor.eval, monitor.path0Day);
|
||||
OneMinuteFetch(envSecrets, monitor.folderName, monitor.url, monitor.method, JSON.stringify(monitor.headers), monitor.body, monitor.timeout, monitor.eval, monitor.path0Day, monitor.path90Day);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" >
|
||||
<html lang="en" class="dark dark:bg-background">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" id="kener-app-favicon" href="/kener.png" />
|
||||
|
||||
@@ -67,6 +67,7 @@
|
||||
},
|
||||
data: {
|
||||
day0: day0File,
|
||||
day90: day90File,
|
||||
tz: Intl.DateTimeFormat().resolvedOptions().timeZone
|
||||
},
|
||||
};
|
||||
@@ -91,7 +92,7 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<section class="mx-auto backdrop-blur-[2px] mb-8 flex w-full max-w-[880px] flex-1 flex-col items-start justify-center">
|
||||
<section class="mx-auto backdrop-blur-[2px] mb-8 flex w-full max-w-[890px] flex-1 flex-col items-start justify-center">
|
||||
<Card.Root class="w-full">
|
||||
<Card.Content>
|
||||
<div class="grid grid-cols-12 gap-4">
|
||||
@@ -115,15 +116,30 @@
|
||||
</div>
|
||||
</div>
|
||||
{#if !loading90}
|
||||
<div class="pt-2">
|
||||
<div class="mt-2">
|
||||
|
||||
|
||||
{#if view == "90day"}
|
||||
<p class="text-sm text-muted-foreground">
|
||||
Uptime for <span class="text-foreground font-semibold">90 Day</span> is {uptime90Day}% and avg latency is {avgLatency90Day} ms
|
||||
90 Day
|
||||
</p>
|
||||
|
||||
<span class="inline-flex items-center rounded-md bg-green-50 px-2 py-1 text-xs font-medium text-green-700 ring-1 ring-inset ring-green-600/20">
|
||||
{uptime90Day}% Uptime
|
||||
</span>
|
||||
<span class="inline-flex items-center rounded-md bg-indigo-50 px-2 py-1 text-xs font-medium text-indigo-700 ring-1 ring-inset ring-indigo-700/10">
|
||||
{avgLatency90Day}ms Latency
|
||||
</span>
|
||||
{:else}
|
||||
<p class="text-sm text-muted-foreground">
|
||||
Uptime for <span class="text-foreground font-semibold">Today</span> is {uptime0Day}% and avg latency is {avgLatency0Day} ms
|
||||
<p class="text-sm text-muted-foreground ">
|
||||
Today
|
||||
</p>
|
||||
<span class="inline-flex items-center rounded-md bg-green-50 px-2 py-1 text-xs font-medium text-green-700 ring-1 ring-inset ring-green-600/20">
|
||||
{uptime0Day}% Uptime
|
||||
</span>
|
||||
<span class="inline-flex items-center rounded-md bg-indigo-50 px-2 py-1 text-xs font-medium text-indigo-700 ring-1 ring-inset ring-indigo-700/10">
|
||||
{avgLatency0Day}ms Latency
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
@@ -171,9 +187,9 @@
|
||||
<div class="absolute show-hover text-sm bg-background">
|
||||
<div class="text-{bar.cssClass} font-semibold" >
|
||||
{#if bar.message != "No Data"}
|
||||
{bar.timestamp} / {bar.uptimePercentage}% up / {bar.avgLatency} ms AVG latency
|
||||
{new Date(bar.timestamp).toLocaleDateString()} ● {bar.message} ● {bar.avgLatency} ms AVG latency
|
||||
{:else}
|
||||
{bar.timestamp} / {bar.message}
|
||||
{bar.timestamp} ● {bar.message}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
@@ -189,7 +205,7 @@
|
||||
</div>
|
||||
<div class="hidden relative">
|
||||
<div data-index="{bar.index}" class="w-[300px] pb-2 pr-1 pl-1 text-sm text-center rounded font-semibold message bg-black text-white border ">
|
||||
<span class="text-{bar.cssClass} text-xl">•</span> {bar.timestamp} / {bar.status} / {bar.latency} ms
|
||||
<span class="text-{bar.cssClass} text-xl">●</span> {bar.timestamp} / {bar.status} / {bar.latency} ms
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
@@ -46,7 +46,16 @@ export PUBLIC_KENER_FOLDER=./build/client/kener
|
||||
export tz=UTC
|
||||
node prod.js
|
||||
```
|
||||
-
|
||||
## Github Setup
|
||||
- Create a Github Repositiory
|
||||
- Go to [Personal Access Token](https://github.com/settings/personal-access-tokens/new)
|
||||
- Create a Fine-grained token
|
||||
- If your repository is private then give Read-Write access to issues
|
||||
- Add the access token as an environment variable
|
||||
```shell
|
||||
export GH_TOKEN=github_pat_11AD3ZA3Y0
|
||||
```
|
||||
|
||||
---
|
||||
# Modify Site
|
||||
|
||||
@@ -132,11 +141,12 @@ Example add a png called `logo.png` file in `static/` and then
|
||||
|
||||
|
||||
## github
|
||||
For incident kener uses github comments. Create an empty [github](https://github.com) public repo and add them to `site.yaml`
|
||||
For incident kener uses github comments. Create an empty [github](https://github.com) repo and add them to `site.yaml`
|
||||
```yaml
|
||||
github:
|
||||
owner: "username"
|
||||
repo: "your-reponame"
|
||||
refer: true
|
||||
```
|
||||
## metaTags
|
||||
Meta tags are nothing but html `<meta>`. You can use them for SEO purposes
|
||||
@@ -346,4 +356,12 @@ Assuming `ORDER_ID` is present in env
|
||||
latency: responseTime,
|
||||
}
|
||||
})
|
||||
```
|
||||
```
|
||||
# Incident Management
|
||||
Kener uses Github to power incident management. We encourage you to create public repositores so that others can subscribe to updates to issues
|
||||
## How to create
|
||||
Create an issue with two labels `your-monitor-tag` and `status`
|
||||

|
||||
|
||||
- Open issues are considered as live incidents.
|
||||
- Add comments and it will showup in kener.
|
||||
@@ -2,6 +2,7 @@
|
||||
import fs from "fs-extra"
|
||||
import { env } from "$env/dynamic/public";
|
||||
import moment from "moment";
|
||||
import Randomstring from "randomstring";
|
||||
const WEBHOOK_TOKEN = process.env.WEBHOOK_TOKEN;
|
||||
const WEBHOOK_IP = process.env.WEBHOOK_IP;
|
||||
|
||||
@@ -63,11 +64,7 @@ const store = function(data, authHeader, ip){
|
||||
|
||||
//read the monitor.path0Day file
|
||||
let day0 = {};
|
||||
try {
|
||||
day0 = JSON.parse(fs.readFileSync(monitor.path0Day, "utf8"));
|
||||
} catch (error) {
|
||||
return { error: "something went wrong", status: 400 };
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -75,22 +72,12 @@ const store = function(data, authHeader, ip){
|
||||
|
||||
day0[timeStampISOMinute] = resp;
|
||||
//sort the keys
|
||||
let keys = Object.keys(day0);
|
||||
keys.sort((a,b) => {
|
||||
return moment(a).isBefore(moment(b)) ? -1 : 1;
|
||||
});
|
||||
let sortedDay0 = {};
|
||||
//oppsite of sort is required and only first 1440 keys are required
|
||||
keys.reverse()
|
||||
.slice(0, 129600)
|
||||
.reverse()
|
||||
.forEach((key) => {
|
||||
sortedDay0[key] = day0[key];
|
||||
});
|
||||
|
||||
|
||||
|
||||
//create a random string with high cardinlity
|
||||
//to avoid cache
|
||||
|
||||
//write the monitor.path0Day file
|
||||
fs.writeFileSync(monitor.path0Day, JSON.stringify(sortedDay0, null, 2));
|
||||
fs.writeFileSync(env.PUBLIC_KENER_FOLDER + `/${monitor.folderName}.webhook.${Randomstring.generate()}.json`, JSON.stringify(day0, null, 2));
|
||||
|
||||
|
||||
return { status: 200, message: "success at " + timeStampISOMinute };
|
||||
|
||||
@@ -67,43 +67,50 @@ export async function POST({ request }) {
|
||||
}
|
||||
|
||||
let day0 = JSON.parse(fs.readFileSync(payload.day0, "utf8"));
|
||||
let _90DayFileData = JSON.parse(fs.readFileSync(payload.day90, "utf8"));
|
||||
//_90Day = {..._90Day, ..._90DayFileData};
|
||||
|
||||
|
||||
for (const timestampISO in _90DayFileData) {
|
||||
let cssClass = statusObj.UP;
|
||||
let message = "OK";
|
||||
const element = _90DayFileData[timestampISO];
|
||||
|
||||
|
||||
let currentDay = moment.tz(timestampISO, tz).format("YYYY-MM-DD");
|
||||
|
||||
if(_90Day[currentDay] === undefined) continue;
|
||||
|
||||
_90Day[currentDay].UP = element.UP;
|
||||
_90Day[currentDay].DEGRADED = element.DEGRADED;
|
||||
_90Day[currentDay].DOWN = element.DOWN;
|
||||
_90Day[currentDay].avgLatency = element.avgLatency;
|
||||
_90Day[currentDay].latency = element.latency;
|
||||
|
||||
_90Day[currentDay].uptimePercentage = parseUptime(element.UP + element.DEGRADED, element.UP + element.DEGRADED + element.DOWN);
|
||||
|
||||
if (element.DEGRADED > 0) {
|
||||
cssClass = statusObj.DEGRADED;
|
||||
message = "Degraded for " + element.DEGRADED + " minutes";
|
||||
}
|
||||
|
||||
if (element.DOWN > 0) {
|
||||
cssClass = statusObj.DOWN;
|
||||
message = "Down for " + element.DOWN + " minutes";
|
||||
}
|
||||
|
||||
_90Day[currentDay].cssClass = cssClass;
|
||||
_90Day[currentDay].message = message;
|
||||
}
|
||||
//loop day0 as object
|
||||
for (const timestampISO in day0) {
|
||||
if (Object.hasOwnProperty.call(day0, timestampISO)) {
|
||||
const element = day0[timestampISO];
|
||||
let min = moment.tz(timestampISO, tz).format("YYYY-MM-DD HH:mm:00");
|
||||
let day = moment.tz(timestampISO, tz).format("YYYY-MM-DD");
|
||||
let status = element.status;
|
||||
let latency = element.latency;
|
||||
|
||||
//90 Day data
|
||||
let d = _90Day[day];
|
||||
_90Day[day] = {
|
||||
timestamp: day,
|
||||
UP: status == "UP" ? d.UP + 1 : d.UP,
|
||||
DEGRADED: status == "DEGRADED" ? d.DEGRADED + 1 : d.DEGRADED,
|
||||
DOWN: status == "DOWN" ? d.DOWN + 1 : d.DOWN,
|
||||
latency: d.latency + latency,
|
||||
avgLatency: ((d.latency + latency) / (d.UP + d.DEGRADED + d.DOWN + 1)).toFixed(0),
|
||||
};
|
||||
_90Day[day].uptimePercentage = parseUptime(_90Day[day].UP + _90Day[day].DEGRADED, _90Day[day].UP + _90Day[day].DEGRADED + _90Day[day].DOWN);
|
||||
|
||||
let cssClass = statusObj.UP;
|
||||
let message = "OK";
|
||||
|
||||
if (_90Day[day].DEGRADED > 0) {
|
||||
cssClass = statusObj.DEGRADED;
|
||||
message = "Degraded for " + _90Day[day].DEGRADED + " minutes";
|
||||
}
|
||||
|
||||
if (_90Day[day].DOWN > 0) {
|
||||
cssClass = statusObj.DOWN;
|
||||
message = "Down for " + _90Day[day].DOWN + " minutes";
|
||||
}
|
||||
|
||||
_90Day[day].cssClass = cssClass;
|
||||
_90Day[day].message = message;
|
||||
|
||||
//0 Day data
|
||||
if (_0Day[min] !== undefined) {
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
<section class="mx-auto container rounded-3xl bg-white mt-32">
|
||||
<div class="grid grid-cols-5 gap-4">
|
||||
<div class="col-span-5 md:col-span-1 hidden md:block border-r-2 border-gray-500">
|
||||
<ul class="w-full sticky top-0 text-sm font-medium text-gray-900 bg-white mt-8 rounded-lg">
|
||||
<ul class="w-full text-sm font-medium text-gray-900 bg-white mt-8 rounded-lg">
|
||||
{#each sideBar as item}
|
||||
<li class="w-full px-4 py-2">
|
||||
<a href="#{item.id}" class="{item.type == 'h2'?'pl-5':''}">{item.text}</a>
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
<Card.Description> {moment(incident.created_at).format("MMMM Do YYYY, h:mm:ss a")} </Card.Description>
|
||||
</Card.Header>
|
||||
<Card.Content>
|
||||
<div class="prose prose-stone max-w-none prose-code:bg-gray-200 prose-code:px-[0.3rem] prose-code:py-[0.2rem] prose-code:font-mono prose-code:text-sm prose-code:rounded">
|
||||
<div class="prose prose-stone dark:prose-invert max-w-none prose-code:bg-gray-200 prose-code:px-[0.3rem] prose-code:py-[0.2rem] prose-code:font-mono prose-code:text-sm prose-code:rounded">
|
||||
{@html incident.body}
|
||||
</div>
|
||||
{#if incident.comments.length > 0}
|
||||
@@ -44,7 +44,7 @@
|
||||
<li class="mb-10 ms-4">
|
||||
<div class="absolute w-3 h-3 rounded-full mt-1.5 -start-1.5 border bg-secondary border-secondary"></div>
|
||||
<time class="mb-1 text-sm font-normal leading-none text-muted-foreground"> {moment(comment.created_at).format("MMMM Do YYYY, h:mm:ss a")} </time>
|
||||
<div class="mb-4 text-base font-normal wysiwyg prose prose-stone max-w-none prose-code:bg-gray-200 prose-code:px-[0.3rem] prose-code:py-[0.2rem] prose-code:font-mono prose-code:text-sm prose-code:rounded">
|
||||
<div class="mb-4 text-base font-normal wysiwyg dark:prose-invert prose prose-stone max-w-none prose-code:px-[0.3rem] prose-code:py-[0.2rem] prose-code:font-mono prose-code:text-sm prose-code:rounded">
|
||||
{@html comment.body}
|
||||
</div>
|
||||
</li>
|
||||
@@ -90,7 +90,7 @@
|
||||
</Collapsible.Trigger>
|
||||
<Collapsible.Content>
|
||||
<Card.Content>
|
||||
<div class="prose prose-stone max-w-none prose-code:bg-gray-200 prose-code:px-[0.3rem] prose-code:py-[0.2rem] prose-code:font-mono prose-code:text-sm prose-code:rounded">
|
||||
<div class="prose prose-stone dark:prose-invert max-w-none prose-code:bg-gray-200 prose-code:px-[0.3rem] prose-code:py-[0.2rem] prose-code:font-mono prose-code:text-sm prose-code:rounded">
|
||||
{@html incident.body}
|
||||
</div>
|
||||
{#if incident.comments.length > 0}
|
||||
|
||||
BIN
static/issue.png
Normal file
BIN
static/issue.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.8 KiB |
Reference in New Issue
Block a user