From 36ede93dce1ba94a3896880e49718d801529b02c Mon Sep 17 00:00:00 2001 From: Raj Nandan Sharma Date: Sat, 25 Jan 2025 21:12:41 +0530 Subject: [PATCH] feat: sitemap and bug fixes --- docs/home.md | 4 +- main.js | 5 + package.json | 2 +- src/lib/components/shareMenu.svelte | 5 +- src/lib/i18n/client.js | 10 +- src/lib/server/controllers/controller.js | 91 +++++++++++++++++++ src/routes/(docs)/+layout.svelte | 3 +- src/routes/(kener)/+page.server.js | 19 +++- src/routes/(kener)/+page.svelte | 14 ++- .../(kener)/incidents/[month]/+page.svelte | 7 ++ 10 files changed, 139 insertions(+), 21 deletions(-) diff --git a/docs/home.md b/docs/home.md index 4cba5109..ea52efbd 100644 --- a/docs/home.md +++ b/docs/home.md @@ -1,6 +1,6 @@ --- -title: Kener - A Sveltekit NodeJS Status Page System -description: Kener is an open-source Node.js status page tool, designed to make service monitoring and incident handling a breeze. It offers a sleek and user-friendly interface that simplifies tracking service outages and improves how we communicate during incidents. +title: Kener Documentation +description: Kener is a feature-rich and modern status page system built with SvelteKit and NodeJS. It is open-source and free to use. --- # Kener - A Feature Rich & Modern Status Page diff --git a/main.js b/main.js index 4f7e8997..c70a5c37 100644 --- a/main.js +++ b/main.js @@ -4,6 +4,7 @@ import dotenv from "dotenv"; dotenv.config(); import express from "express"; import Startup from "./src/lib/server/startup.js"; +import { GetSiteMap } from "./src/lib/server/controllers/controller.js"; import fs from "fs-extra"; import knex from "knex"; import knexOb from "./knexfile.js"; @@ -24,6 +25,10 @@ app.use((req, res, next) => { app.get(base + "/healthcheck", (req, res) => { res.end("ok"); }); +app.get(base + "/sitemap.xml", async (req, res) => { + res.header("Content-Type", "application/xml"); + res.send(await GetSiteMap()); +}); //part /uploads server static files from static/uploads //set env variable for upload path diff --git a/package.json b/package.json index 579996b0..1256d5bd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "kener", - "version": "3.0.3", + "version": "3.0.4", "private": false, "license": "MIT", "description": "Kener: An open-source Node.js status page application for real-time service monitoring, incident management, and customizable reporting. Simplify service outage tracking, enhance incident communication, and ensure a seamless user experience.", diff --git a/src/lib/components/shareMenu.svelte b/src/lib/components/shareMenu.svelte index ce43bfe2..e71731fe 100644 --- a/src/lib/components/shareMenu.svelte +++ b/src/lib/components/shareMenu.svelte @@ -169,10 +169,7 @@ {l(lang, "Embed")}

- {@html l( - lang, - "Embed this moni2tor using <script> or <iframe> in your app." - )} + {l(lang, "Embed this monitor using <script> or <iframe> in your app.")}

diff --git a/src/lib/i18n/client.js b/src/lib/i18n/client.js index 1065d5ec..50229027 100644 --- a/src/lib/i18n/client.js +++ b/src/lib/i18n/client.js @@ -27,15 +27,8 @@ const fdm = function (duration, locale) { }; const l = function (sessionLangMap, key, args = {}) { - const keys = key.split("."); - let obj = sessionLangMap; + let obj = sessionLangMap[key]; - for (const keyPart of keys) { - obj = obj?.[keyPart]; - if (!obj) { - break; - } - } // Replace placeholders in the string using the args object if (obj && typeof obj === "string") { obj = obj.replace(/%\w+/g, (placeholder) => { @@ -43,7 +36,6 @@ const l = function (sessionLangMap, key, args = {}) { return args[argKey] !== undefined ? args[argKey] : placeholder; }); } - return obj || key; }; const summaryTime = function (summaryStatus) { diff --git a/src/lib/server/controllers/controller.js b/src/lib/server/controllers/controller.js index a3f28ff1..420b4005 100644 --- a/src/lib/server/controllers/controller.js +++ b/src/lib/server/controllers/controller.js @@ -16,6 +16,7 @@ import db from "../db/db.js"; import bcrypt from "bcrypt"; import jwt from "jsonwebtoken"; import crypto from "crypto"; +import { format, subMonths, addMonths, startOfMonth } from "date-fns"; const saltRounds = 10; const DUMMY_SECRET = "DUMMY_SECRET"; @@ -805,3 +806,93 @@ export const IsLoggedInSession = async (cookies) => { user: userDB }; }; + +export const GetSiteMap = async (cookies) => { + let siteMapData = []; + let siteURLData = await GetSiteDataByKey("siteURL"); + let categories = await GetSiteDataByKey("categories"); + let navs = await GetSiteDataByKey("nav"); + if (!!siteURLData) { + siteMapData.push({ + url: siteURLData, + lastmod: new Date().toISOString(), + priority: 1 + }); + } + + //get today's date in January-2025 format date-fns + const today = format(new Date(), "MMMM-yyyy"); + //last month + const lastMonth = format(subMonths(new Date(), 1), "MMMM-yyyy", { addMonths: -1 }); + const nextMonth = format(addMonths(new Date(), 1), "MMMM-yyyy", { addMonths: -1 }); + + siteMapData.push({ + url: siteURLData + "/incidents/" + today, + lastmod: startOfMonth(new Date()).toISOString(), + priority: 0.9 + }); + siteMapData.push({ + url: siteURLData + "/incidents/" + lastMonth, + lastmod: startOfMonth(new Date()).toISOString(), + priority: 0.9 + }); + siteMapData.push({ + url: siteURLData + "/incidents/" + nextMonth, + lastmod: startOfMonth(new Date()).toISOString(), + priority: 0.9 + }); + + if (!!categories) { + for (let i = 0; i < categories.length; i++) { + if (categories[i].name !== "Home") { + siteMapData.push({ + url: siteURLData + "?category=" + categories[i].name, + lastmod: new Date().toISOString(), + priority: 0.9 + }); + } + } + } + if (!!navs) { + for (let i = 0; i < navs.length; i++) { + if (navs[i].url.startsWith(siteURLData)) { + siteMapData.push({ + url: navs[i].url, + lastmod: new Date().toISOString(), + priority: 0.9 + }); + } else if (navs[i].url.startsWith("/")) { + siteMapData.push({ + url: siteURLData + navs[i].url, + lastmod: new Date().toISOString(), + priority: 0.9 + }); + } + } + } + + let monitors = await GetMonitors({ status: "ACTIVE" }); + + for (let i = 0; i < monitors.length; i++) { + siteMapData.push({ + url: siteURLData + "?monitor=?" + monitors[i].tag, + lastmod: new Date(monitors[i].updated_at).toISOString(), + priority: 0.8 + }); + } + + return ` + + ${siteMapData + .map( + (page) => ` + + ${page.url} + ${page.lastmod} + ${page.priority} + + ` + ) + .join("")} +`; +}; diff --git a/src/routes/(docs)/+layout.svelte b/src/routes/(docs)/+layout.svelte index 7276412a..25b93774 100644 --- a/src/routes/(docs)/+layout.svelte +++ b/src/routes/(docs)/+layout.svelte @@ -52,6 +52,7 @@ + Kener Documentation @@ -86,7 +87,7 @@ Kener Documentation - v3.0.3 + v3.0.4
diff --git a/src/routes/(kener)/+page.server.js b/src/routes/(kener)/+page.server.js index ef8d3185..af79af6b 100644 --- a/src/routes/(kener)/+page.server.js +++ b/src/routes/(kener)/+page.server.js @@ -10,6 +10,8 @@ export async function load({ parent, url }) { const requiredCategory = query.get("category") || "Home"; const parentData = await parent(); const siteData = parentData.site; + let pageTitle = siteData.title; + let pageDescription = ""; monitors = SortMonitor(siteData.monitorSort, monitors); const monitorsActive = []; for (let i = 0; i < monitors.length; i++) { @@ -50,6 +52,19 @@ export async function load({ parent, url }) { //if not home page let isCategoryPage = !!query.get("category") && query.get("category") !== "Home"; let isMonitorPage = !!query.get("monitor"); + if (isMonitorPage && monitorsActive.length > 0) { + pageTitle = monitorsActive[0].name + " - " + pageTitle; + pageDescription = monitorsActive[0].description; + } + //if category page + if (isCategoryPage) { + let allCategories = siteData.categories; + let selectedCategory = allCategories.find((category) => category.name === requiredCategory); + if (selectedCategory) { + pageTitle = selectedCategory.name + " - " + pageTitle; + pageDescription = selectedCategory.description; + } + } if (isCategoryPage || isMonitorPage) { let eligibleTags = monitorsActive.map((monitor) => monitor.tag); //filter incidents that have monitor_tag in monitors @@ -90,6 +105,8 @@ export async function load({ parent, url }) { unresolvedIncidents: allOpenIncidents, categoryName: requiredCategory, isCategoryPage: isCategoryPage, - isMonitorPage: isMonitorPage + isMonitorPage: isMonitorPage, + pageTitle: pageTitle, + pageDescription: pageDescription }; } diff --git a/src/routes/(kener)/+page.svelte b/src/routes/(kener)/+page.svelte index f3e96661..459ecc15 100644 --- a/src/routes/(kener)/+page.svelte +++ b/src/routes/(kener)/+page.svelte @@ -34,8 +34,10 @@ if (data.isCategoryPage) { let category = data.site.categories.find((e) => e.name == data.categoryName); - data.site.hero.title = category.name; - data.site.hero.subtitle = category.description; + if (!!category) { + data.site.hero.title = category.name; + data.site.hero.subtitle = category.description; + } } onMount(() => { @@ -43,6 +45,12 @@ }); + + {data.pageTitle} + {#if !!data.pageDescription} + + {/if} +
{#if data.site.hero && !data.isMonitorPage}
- + {category.name} diff --git a/src/routes/(kener)/incidents/[month]/+page.svelte b/src/routes/(kener)/incidents/[month]/+page.svelte index 980cb50c..54fafba1 100644 --- a/src/routes/(kener)/incidents/[month]/+page.svelte +++ b/src/routes/(kener)/incidents/[month]/+page.svelte @@ -50,6 +50,13 @@ let sortedIncidentSmartDates = Object.keys(incidentSmartDates).sort((a, b) => a - b); + + + {f(parse(data.thisMonthName, "MMMM-yyyy", new Date()), "MMMM, yyyy", selectedLang)} + {l(data.lang, "Incident Updates")} | + {data.site.title} + +