feat: implement dynamic versioning in headers and documentation as reported in #346

This commit is contained in:
Raj Nandan Sharma
2025-03-22 21:54:24 +05:30
parent 7e182b1df2
commit ccdeff98dc
10 changed files with 128 additions and 109 deletions
+2 -1
View File
@@ -3,6 +3,7 @@ import Mustache from "mustache";
import { DiscordJSONTemplate } from "../../anywhere.js";
import variables from "./variables.js";
import { GetRequiredSecrets, ReplaceAllOccurrences } from "../tool.js";
import version from "../../version.js";
class Discord {
url;
@@ -15,7 +16,7 @@ class Discord {
constructor(url, siteData, monitorData, trigger_meta) {
const kenerHeader = {
"Content-Type": "application/json",
"User-Agent": "Kener",
"User-Agent": `Kener/${version()}`,
};
this.url = url;
+76 -75
View File
@@ -3,56 +3,57 @@ import { Resend } from "resend";
import nodemailer from "nodemailer";
import getSMTPTransport from "./smtps.js";
import { GetRequiredSecrets, ReplaceAllOccurrences } from "../tool.js";
import version from "../../version.js";
class Email {
to;
from;
method;
siteData;
monitorData;
meta;
to;
from;
method;
siteData;
monitorData;
meta;
constructor(meta, siteData, monitorData) {
this.to = meta.to;
this.from = meta.from;
this.siteData = siteData;
this.monitorData = monitorData;
constructor(meta, siteData, monitorData) {
this.to = meta.to;
this.from = meta.from;
this.siteData = siteData;
this.monitorData = monitorData;
let metaString = JSON.stringify(meta);
let metaString = JSON.stringify(meta);
let envSecrets = GetRequiredSecrets(`${JSON.stringify(meta)}`);
let envSecrets = GetRequiredSecrets(`${JSON.stringify(meta)}`);
for (let i = 0; i < envSecrets.length; i++) {
const secret = envSecrets[i];
metaString = ReplaceAllOccurrences(metaString, secret.find, secret.replace);
}
for (let i = 0; i < envSecrets.length; i++) {
const secret = envSecrets[i];
metaString = ReplaceAllOccurrences(metaString, secret.find, secret.replace);
}
this.meta = JSON.parse(metaString);
}
this.meta = JSON.parse(metaString);
}
transformData(data) {
let ctaLink = data.actions[0].url;
let ctaText = data.actions[0].text;
let emailApiRequest = {
from: this.from,
to: this.to.split(",").map((email) => email.trim()),
subject: `[${data.status}] ${data.alert_name} at ${data.timestamp}`,
text: `Alert ${data.alert_name} has been ${data.status} at ${data.timestamp}. Click here to view the alert: ${ctaLink}`,
html: ""
};
transformData(data) {
let ctaLink = data.actions[0].url;
let ctaText = data.actions[0].text;
let emailApiRequest = {
from: this.from,
to: this.to.split(",").map((email) => email.trim()),
subject: `[${data.status}] ${data.alert_name} at ${data.timestamp}`,
text: `Alert ${data.alert_name} has been ${data.status} at ${data.timestamp}. Click here to view the alert: ${ctaLink}`,
html: "",
};
let bgColor = "#f4f4f4";
if (data.severity == "critical") {
bgColor = this.siteData.colors.DOWN;
} else if (data.severity == "warning") {
bgColor = this.siteData.colors.DEGRADED;
}
let bgColor = "#f4f4f4";
if (data.severity == "critical") {
bgColor = this.siteData.colors.DOWN;
} else if (data.severity == "warning") {
bgColor = this.siteData.colors.DEGRADED;
}
if (data.status === "RESOLVED") {
bgColor = this.siteData.colors.UP;
}
if (data.status === "RESOLVED") {
bgColor = this.siteData.colors.UP;
}
let html = `
let html = `
<!DOCTYPE html>
<html>
<head>
@@ -177,53 +178,53 @@ class Email {
</table>
<div class="footer">
This is an automated alert notification from ${this.siteData.siteName} monitoring system.
This is an automated alert notification from ${this.siteData.siteName} monitoring system. It is being powered by <a href='https://kener.ing'>Kener</a> v${version()}.
</div>
</div>
</body>
</html>
`;
emailApiRequest.html = html;
emailApiRequest.html = html;
return emailApiRequest;
}
return emailApiRequest;
}
type() {
return "email";
}
type() {
return "email";
}
async send(data) {
let emailBody = this.transformData(data); // object containing email data (to, subject, text, html, etc)
if (!this.meta.email_type || this.meta.email_type === "resend") {
const resend = new Resend(process.env.RESEND_API_KEY);
try {
return await resend.emails.send(emailBody);
} catch (error) {
console.error("Error sending webhook", error);
return error;
}
}
if (this.meta.email_type === "smtp") {
// Configure the SMTP transporter using environment variables
const transporter = getSMTPTransport(this.meta);
async send(data) {
let emailBody = this.transformData(data); // object containing email data (to, subject, text, html, etc)
if (!this.meta.email_type || this.meta.email_type === "resend") {
const resend = new Resend(process.env.RESEND_API_KEY);
try {
return await resend.emails.send(emailBody);
} catch (error) {
console.error("Error sending webhook", error);
return error;
}
}
if (this.meta.email_type === "smtp") {
// Configure the SMTP transporter using environment variables
const transporter = getSMTPTransport(this.meta);
const mailOptions = {
from: emailBody.from, // sender address
to: Array.isArray(emailBody.to) ? emailBody.to.join(",") : emailBody.to, // recipient address(es)
subject: emailBody.subject, // email subject
text: emailBody.text, // plain text body
html: emailBody.html // HTML body (if any)
};
const mailOptions = {
from: emailBody.from, // sender address
to: Array.isArray(emailBody.to) ? emailBody.to.join(",") : emailBody.to, // recipient address(es)
subject: emailBody.subject, // email subject
text: emailBody.text, // plain text body
html: emailBody.html, // HTML body (if any)
};
try {
return await transporter.sendMail(mailOptions);
} catch (error) {
console.error("Error sending email via SMTP", error);
return error;
}
}
}
try {
return await transporter.sendMail(mailOptions);
} catch (error) {
console.error("Error sending email via SMTP", error);
return error;
}
}
}
}
export default Email;
+2 -1
View File
@@ -3,6 +3,7 @@ import variables from "./variables.js";
import { SlackJSONTemplate } from "../../anywhere.js";
import Mustache from "mustache";
import { GetRequiredSecrets, ReplaceAllOccurrences } from "../tool.js";
import version from "../../version.js";
class Slack {
url;
@@ -15,7 +16,7 @@ class Slack {
constructor(url, siteData, monitorData, trigger_meta) {
const kenerHeader = {
"Content-Type": "application/json",
"User-Agent": "Kener",
"User-Agent": `Kener/${version()}`,
};
this.url = url;
+2 -1
View File
@@ -4,6 +4,7 @@ import { GetRequiredSecrets, ReplaceAllOccurrences } from "../tool.js";
import Mustache from "mustache";
import { WebhookJSONTemplate } from "../../anywhere.js";
import variables from "./variables.js";
import version from "../../version.js";
class Webhook {
url;
@@ -16,7 +17,7 @@ class Webhook {
constructor(trigger_meta, method, siteData, monitorData) {
const kenerHeader = {
"Content-Type": "application/json",
"User-Agent": "Kener/3.2.5",
"User-Agent": `Kener/${version()}`,
};
let headers = trigger_meta.headers;
this.trigger_meta = trigger_meta;
+3 -18
View File
@@ -9,25 +9,12 @@ import { HashString } from "./tool.js";
import { fileURLToPath } from "url";
import { dirname, resolve } from "path";
import fs from "fs";
import version from "../version.js";
const jobs = [];
process.env.TZ = "UTC";
let isStartUP = true;
// Get the version from package.json
const getVersion = () => {
try {
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const packagePath = resolve(__dirname, "../../../package.json");
const packageJson = JSON.parse(fs.readFileSync(packagePath, "utf8"));
return packageJson.version;
} catch (error) {
console.error("Error reading version:", error);
return "unknown";
}
};
const scheduleCronJobs = async () => {
// Fetch and map all active monitors, creating a unique hash for each
const activeMonitors = (await GetMonitorsParsed({ status: "ACTIVE" })).map((monitor) => ({
@@ -86,15 +73,13 @@ async function Startup() {
mainJob.trigger();
const version = getVersion();
figlet("Kener v" + version, function (err, data) {
figlet("Kener v" + version(), function (err, data) {
if (err) {
console.log("Something went wrong...");
return;
}
console.log(data);
console.log(`Kener version ${version} is running!`);
console.log(`Kener version ${version()} is running!`);
});
}
+31
View File
@@ -0,0 +1,31 @@
import { fileURLToPath } from "url";
import { dirname, resolve } from "path";
import fs from "fs";
function getVersionUsingVite() {
try {
if (!!import.meta.env.PACKAGE_VERSION) {
return import.meta.env.PACKAGE_VERSION;
}
return null;
} catch (e) {
return null;
}
}
export default function version() {
let v = getVersionUsingVite();
if (!!v) {
return v;
} else {
try {
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const packagePath = resolve(__dirname, "../../package.json");
const packageJson = JSON.parse(fs.readFileSync(packagePath, "utf8"));
return packageJson.version;
} catch (e) {
return "0.0.0";
}
}
}
+4 -7
View File
@@ -2,6 +2,7 @@
import fs from "fs-extra";
import path from "path";
import { marked } from "marked";
import version from "$lib/version.js";
function extractMetadataAndContent(fileContent) {
// Regular expression to match metadata section
@@ -15,9 +16,7 @@ function extractMetadataAndContent(fileContent) {
if (match) {
// Extract metadata section and remaining content
const metadataLines = match[1]
.split("\n")
.filter((line) => line.trim() !== "");
const metadataLines = match[1].split("\n").filter((line) => line.trim() !== "");
result.content = fileContent.slice(match[0].length).trim(); // Remaining content after metadata
metadataLines.forEach((line) => {
@@ -40,10 +39,7 @@ export async function load({ params, route, url, cookies, request }) {
docFilePath = docFolderPath + `/${docFile}.md`;
}
const fileContents = await fs.readFileSync(docFilePath, "utf-8");
const siteStructure = await fs.readFileSync(
docFolderPath + "/structure.json",
"utf-8",
);
const siteStructure = await fs.readFileSync(docFolderPath + "/structure.json", "utf-8");
const { metadata, content } = extractMetadataAndContent(fileContents);
const selectedDoc = docFilePath.replace(docFolderPath, "");
@@ -54,5 +50,6 @@ export async function load({ params, route, url, cookies, request }) {
title: metadata.title || "Kener Docs",
description: metadata.description || "Kener Docs",
siteStructure: siteStructureJSON,
kenerVersion: version(),
};
}
+2 -1
View File
@@ -11,6 +11,7 @@
import Moon from "lucide-svelte/icons/moon";
import { onMount } from "svelte";
import { base } from "$app/paths";
import { page } from "$app/stores";
let defaultTheme = "light";
export let data;
let siteStructure = data.siteStructure;
@@ -111,7 +112,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">
{import.meta.env.PACKAGE_VERSION}
{$page.data.kenerVersion}
</span>
</a>
</div>
@@ -4,6 +4,7 @@ import { redirect } from "@sveltejs/kit";
import { base } from "$app/paths";
import { MaskString } from "$lib/server/tool.js";
import db from "$lib/server/db/db.js";
import version from "$lib/version.js";
//write a function to mask a string, just have last 4 characters visible
export async function load({ params, route, url, cookies, request }) {
@@ -25,5 +26,6 @@ export async function load({ params, route, url, cookies, request }) {
siteData,
KENER_SECRET_KEY: !!process.env.KENER_SECRET_KEY ? MaskString(process.env.KENER_SECRET_KEY) : "",
user: userDB,
kenerVersion: version(),
};
}
@@ -9,9 +9,7 @@
import Github from "lucide-svelte/icons/github";
import { Button } from "$lib/components/ui/button";
import { afterNavigate } from "$app/navigation";
const version = import.meta.env.PACKAGE_VERSION;
import { page } from "$app/stores";
import { Play, User } from "lucide-svelte";
import { setMode, mode, ModeWatcher } from "mode-watcher";
@@ -23,7 +21,6 @@
setMode("light");
}
}
// setMode("dark");
let nav = [
@@ -168,8 +165,10 @@
<a
target="_blank"
class="text-xs font-semibold text-muted-foreground hover:underline"
href="https://kener.ing/docs/changelogs#v{version.replaceAll('.', '-')}">v{version}</a
href="https://kener.ing/docs/changelogs#v{$page.data.kenerVersion.replaceAll('.', '-')}"
>
v{$page.data.kenerVersion}
</a>
by
<a
target="_blank"