diff --git a/.env.example b/.env.example new file mode 100644 index 000000000..264a84aa9 --- /dev/null +++ b/.env.example @@ -0,0 +1,7 @@ +NODE_ENV=production +PORT=3000 +GH_TOKEN=your_github_token +API_TOKEN=your_api_token +API_IP="" +API_IP_REGEX="" +KENER_BASE_PATH="" \ No newline at end of file diff --git a/.github/workflows/publishImage.yml b/.github/workflows/publishImage.yml index d2453f048..cc95914bc 100644 --- a/.github/workflows/publishImage.yml +++ b/.github/workflows/publishImage.yml @@ -1,40 +1,32 @@ +--- name: Publish Docker image to Dockerhub and GHCR - on: push: branches: - - 'main' - # add additional branches that should build to images here - # they will be tagged based on the branch name IE kener:test - #- 'test' + - main tags: - - '*.*.*' - # don't trigger if just updating docs + - "*.*.*" paths-ignore: - - 'docs.md' - - 'README.md' - + - docs.md + - README.md jobs: push_to_registry: name: Push Docker image to Docker Hub runs-on: ubuntu-latest - # only run if we've specified image tag to push to if: ${{ vars.DOCKERHUB_IMAGE_NAME != '' || vars.GHCR_IMAGE_NAME != '' }} - # https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions-for-the-github_token permissions: packages: write contents: read steps: - name: Check out the repo uses: actions/checkout@v2 - - name: Log in to Docker Hub - if: ${{ github.event_name != 'pull_request' && vars.DOCKERHUB_IMAGE_NAME != '' }} + if: ${{ github.event_name != 'pull_request' && vars.DOCKERHUB_IMAGE_NAME != '' + }} uses: docker/login-action@v2 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - - name: Login to GitHub Container Registry if: ${{ github.event_name != 'pull_request' && vars.GHCR_IMAGE_NAME != '' }} uses: docker/login-action@v2 @@ -42,7 +34,6 @@ jobs: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Extract metadata (tags, labels) for Docker id: meta uses: docker/metadata-action@v3 @@ -50,20 +41,16 @@ jobs: images: | ${{ vars.DOCKERHUB_IMAGE_NAME }} ${{ vars.GHCR_IMAGE_NAME }} - # generate Docker tags based on the following events/attributes tags: | type=raw,value=latest,enable=${{ endsWith(github.ref, 'main') }} type=ref,event=branch,enable=${{ !endsWith(github.ref, 'main') }} type=semver,pattern={{version}} flavor: | latest=false - - name: Set up QEMU uses: docker/setup-qemu-action@v2 - - name: Set up Docker Buildx uses: docker/setup-buildx-action@v2 - - name: Build and push Docker image uses: docker/build-push-action@v4 with: diff --git a/.gitignore b/.gitignore index 0f0f68ff4..caa9c131e 100644 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,10 @@ vite.config.ts.timestamp-* nodemon.json .okgit/ config/static/* -!config/static/.kener \ No newline at end of file +!config/static/.kener +db/* +!db/.kener +database/* +!database/.kener +src/lib/server/config/monitors.yaml +src/lib/server/config/site.yaml \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index a3581b95c..ef4e95084 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,16 +15,11 @@ RUN \ # set OS timezone specified by docker ENV RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone -ARG data_dir=/config -VOLUME $data_dir -ENV CONFIG_DIR=$data_dir + COPY docker/root/ / -# Dir ENVs need to be set before building or else build throws errors -ENV PUBLIC_KENER_FOLDER=/config/static \ - MONITOR_YAML_PATH=/config/monitors.yaml \ - SITE_YAML_PATH=/config/site.yaml + # build requires devDependencies which are not used by production deploy # so build in a stage so we can copy results to clean "deploy" stage later @@ -34,12 +29,9 @@ WORKDIR /app COPY --chown=abc:abc . /app -# build requires PUBLIC_KENER_FOLDER dir exists so create temporarily -# -- it is non-existent in final stage to allow proper startup and chown'ing/example population -RUN mkdir -p "${CONFIG_DIR}"/static \ - && npm install \ +RUN npm install \ && chown -R root:root node_modules \ - && npm run kener:build + && npm run build FROM base as app @@ -48,13 +40,16 @@ FROM base as app COPY --chown=abc:abc package*.json ./ COPY --from=base /usr/local/bin /usr/local/bin COPY --from=base /usr/local/lib /usr/local/lib -COPY --chown=abc:abc scripts /app/scripts + COPY --chown=abc:abc static /app/static -COPY --chown=abc:abc locales /app/locales -COPY --chown=abc:abc config /app/config +COPY --chown=abc:abc database /app/database +COPY --chown=abc:abc build.js /app/build.js +COPY --chown=abc:abc sitemap.js /app/sitemap.js +COPY --chown=abc:abc src/lib/server /app/src/lib/server COPY --chown=abc:abc src/lib/helpers.js /app/src/lib/helpers.js + COPY --from=build --chown=abc:abc /app/build /app/build -COPY --from=build --chown=abc:abc /app/prod.js /app/prod.js +COPY --from=build --chown=abc:abc /app/main.js /app/main.js ENV NODE_ENV=production diff --git a/README.md b/README.md index 9fc6778da..a76269506 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ +# Kener - A Sveltekit NodeJS Status Page System +
- +
-#### 👉 Visit a live server [here](https://kener.ing) +#### [👉 Visit a live server](https://kener.ing) + +#### [👉 Quick Start](https://kener.ing/docs/quick-start) + +#### [👉 Documentation](https://kener.ing/docs) -#### 👉 Read the documentation [here](https://kener.ing/kener-docs) +## What is Kener? -# Kener - Status Page System +Kener: Open-source sveltekit 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. Kener integrates seamlessly with GitHub, making incident management a team effort—making. -Kener: 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. And the best part? Kener integrates seamlessly with GitHub, making incident management a team effort—making it easier for us to track and fix issues together in a collaborative and friendly environment. +It uses files to store the data. -It uses files to store the data. Other adapters are coming soon +Kener name is derived from the word "Kene" which means "how is it going" in Assamese, then .ing is added to make cooler. ## Features -**Monitoring and Tracking:** +### Monitoring and Tracking - Real-time monitoring - Polls HTTP endpoint or Push data to monitor using Rest APIs @@ -29,11 +35,11 @@ It uses files to store the data. Other adapters are coming soon - Cron-based scheduling for monitors. Minimum per minute - Flexible monitor configuration using YAML. Define your own parsing for monitor being UP/DOWN/DEGRADED - Construct complex API Polls - Chain, Secrets etc -- Supports a Default Status for Monitors. Example defaultStatus=DOWN if you dont hit API per minute with Status UP +- Supports a Default Status for Monitors. Example defaultStatus=DOWN if you don't hit API per minute with Status UP - Supports base path for hosting in k8s - Pre-built docker image for easy deployment -**Customization and Branding:** +### Customization and Branding - Customizable status page using yaml or code - Badge generation for status and uptime of Monitors @@ -42,12 +48,12 @@ It uses files to store the data. Other adapters are coming soon - Light + Dark Theme - Internationalization support -**Incident Management:** +### Incident Management - Create Incidents using Github Issues - Rich Text - Or use APIs to create Incidents -**User Experience and Design:** +### User Experience and Design - 100% Accessibility Score - Easy installation and setup @@ -64,13 +70,6 @@ It uses files to store the data. Other adapters are coming soon - [Upptime](https://upptime.js.org/) -## Roadmap - -- [x] Add api to create incident -- [x] Add docker file -- [ ] Add notification -- [ ] Add Mysql adapter - ## Screenshots ![image](static/marken_90.png) @@ -85,8 +84,8 @@ It uses files to store the data. Other adapters are coming soon ## Support Me -[Sponsor Me](https://github.com/sponsors/rajnandan1) +If you are using Kener and want to support me, you can do so by sponsoring me on GitHub or buying me a coffee. - +[Sponsor Me Using Github](https://github.com/sponsors/rajnandan1) - +[Buy Me a Coffee](https://www.buymeacoffee.com/rajnandan1) diff --git a/scripts/startup.js b/build.js similarity index 60% rename from scripts/startup.js rename to build.js index 8e8042ebb..4329052fe 100644 --- a/scripts/startup.js +++ b/build.js @@ -1,29 +1,17 @@ -/* -The startup js script will -check if monitors.yaml exists -if it does, it will read the file and parse it into a json array of objects -each objects will have a name, url, method: required -name of each of these objects need to be unique -*/ -import * as dotenv from "dotenv"; -import fs from "fs-extra"; import yaml from "js-yaml"; -import { Cron } from "croner"; -import { FOLDER, FOLDER_MONITOR, FOLDER_SITE, API_TIMEOUT } from "./constants.js"; +import fs from "fs-extra"; +import axios from "axios"; import { IsValidURL, + checkIfDuplicateExists, + getWordsStartingWithDollar, IsValidHTTPMethod, - LoadMonitorsPath, - LoadSitePath, ValidateIpAddress -} from "./tool.js"; -import { GetAllGHLabels, CreateGHLabel } from "./github.js"; -import { Minuter } from "./cron-minute.js"; -import axios from "axios"; -import { Ninety } from "./ninety.js"; -let monitors = []; -let site = {}; -const envSecrets = []; +} from "./src/lib/server/tool.js"; +import { API_TIMEOUT, AnalyticsProviders } from "./src/lib/server/constants.js"; + +const configPathFolder = "./config"; +const databaseFolder = process.argv[2] || "./database"; const defaultEval = `(function (statusCode, responseTime, responseData) { let statusCodeShort = Math.floor(statusCode/100); if(statusCode == 429 || (statusCodeShort >=2 && statusCodeShort <= 3)) { @@ -37,37 +25,34 @@ const defaultEval = `(function (statusCode, responseTime, responseData) { latency: responseTime, } })`; - -function checkIfDuplicateExists(arr) { - return new Set(arr).size !== arr.length; -} -function getWordsStartingWithDollar(text) { - const regex = /\$\w+/g; - const wordsArray = text.match(regex); - return wordsArray || []; -} -if (!fs.existsSync(FOLDER)) { - fs.mkdirSync(FOLDER); - console.log(".kener folder created successfully!"); -} - -const Startup = async () => { +async function Build() { + console.log("Building Kener..."); + let site = {}; + let monitors = []; try { - const fileContent = fs.readFileSync(LoadMonitorsPath(), "utf8"); - site = yaml.load(fs.readFileSync(LoadSitePath(), "utf8")); - monitors = yaml.load(fileContent); + site = yaml.load(fs.readFileSync(configPathFolder + "/site.yaml", "utf8")); + monitors = yaml.load(fs.readFileSync(configPathFolder + "/monitors.yaml", "utf8")); } catch (error) { console.log(error); process.exit(1); } - // Use the 'monitors' array of JSON objects as needed - //check if each object has name, url, method - //if not, exit with error - //if yes, check if name is unique + if ( + site.github === undefined || + site.github.owner === undefined || + site.github.repo === undefined + ) { + console.log("github owner and repo are required"); + process.exit(1); + } + + const FOLDER_DB = databaseFolder; + const FOLDER_SITE = FOLDER_DB + "/site.json"; + const FOLDER_MONITOR = FOLDER_DB + "/monitors.json"; for (let i = 0; i < monitors.length; i++) { const monitor = monitors[i]; + let name = monitor.name; let tag = monitor.tag; let hasAPI = monitor.api !== undefined && monitor.api !== null; @@ -143,6 +128,7 @@ const Startup = async () => { let evaluator = monitor.api.eval; let body = monitor.api.body; let timeout = monitor.api.timeout; + let hideURLForGet = !!monitor.api.hideURLForGet; //url if (!!url) { if (!IsValidURL(url)) { @@ -168,7 +154,7 @@ const Startup = async () => { try { JSON.parse(JSON.stringify(headers)); } catch (error) { - console.log("headers are not valid. Quiting"); + console.log("headers are not valid. Quitting"); process.exit(1); } } @@ -213,9 +199,10 @@ const Startup = async () => { } //add a description to the monitor if it is website using api.url and method = GET and headers == undefined - //call the it to see if recevied content-type is text/html + //call the it to see if received content-type is text/html //if yes, append to description if ( + !hideURLForGet && (headers === undefined || headers === null) && url !== undefined && method === "GET" @@ -240,8 +227,8 @@ const Startup = async () => { } } - monitors[i].path0Day = `${FOLDER}/${folderName}.0day.utc.json`; - monitors[i].path90Day = `${FOLDER}/${folderName}.90day.utc.json`; + monitors[i].path0Day = `${FOLDER_DB}/${folderName}.0day.utc.json`; + monitors[i].path90Day = `${FOLDER_DB}/${folderName}.90day.utc.json`; monitors[i].hasAPI = hasAPI; //secrets can be in url/body/headers @@ -260,16 +247,34 @@ const Startup = async () => { } } } - if ( - site.github === undefined || - site.github.owner === undefined || - site.github.repo === undefined - ) { - console.log("github owner and repo are required"); - process.exit(1); - } + if (site.github.incidentSince === undefined || site.github.incidentSince === null) { - site.github.incidentSince = 48; + site.github.incidentSince = 720; + } + if (site.siteName === undefined) { + site.siteName = site.title; + } + if (!!site.analytics) { + const providers = {}; + + for (let i = 0; i < site.analytics.length; i++) { + const element = site.analytics[i]; + if (!!AnalyticsProviders[element.type]) { + if (providers[element.type] === undefined) { + providers[element.type] = {}; + providers[element.type].measurementIds = []; + providers[element.type].script = AnalyticsProviders[element.type]; + } + providers[element.type].measurementIds.push(element.id); + } + } + site.analytics = providers; + } + if (!!!site.font || !!!site.font.cssSrc || !!!site.font.family) { + site.font = { + cssSrc: "https://fonts.googleapis.com/css2?family=Albert+Sans:ital,wght@0,100..900;1,100..900&display=swap", + family: "Albert Sans" + }; } if (checkIfDuplicateExists(monitors.map((monitor) => monitor.folderName)) === true) { console.log("duplicate monitor detected"); @@ -290,107 +295,6 @@ const Startup = async () => { console.log(error); process.exit(1); } +} - if (!!site.github && !!site.github.owner && !!site.github.repo) { - const ghowner = site.github.owner; - const ghrepo = site.github.repo; - const ghlabels = await GetAllGHLabels(ghowner, ghrepo); - const tagsAndDescription = monitors.map((monitor) => { - return { tag: monitor.tag, description: monitor.name }; - }); - //add incident label if does not exist - - if (ghlabels.indexOf("incident") === -1) { - await CreateGHLabel(ghowner, ghrepo, "incident", "Status of the site"); - } - if (ghlabels.indexOf("resolved") === -1) { - await CreateGHLabel(ghowner, ghrepo, "resolved", "Incident is resolved", "65dba6"); - } - if (ghlabels.indexOf("identified") === -1) { - await CreateGHLabel(ghowner, ghrepo, "identified", "Incident is Identified", "EBE3D5"); - } - if (ghlabels.indexOf("investigating") === -1) { - await CreateGHLabel( - ghowner, - ghrepo, - "investigating", - "Incident is investigated", - "D4E2D4" - ); - } - if (ghlabels.indexOf("incident-degraded") === -1) { - await CreateGHLabel( - ghowner, - ghrepo, - "incident-degraded", - "Status is degraded of the site", - "f5ba60" - ); - } - if (ghlabels.indexOf("incident-down") === -1) { - await CreateGHLabel( - ghowner, - ghrepo, - "incident-down", - "Status is down of the site", - "ea3462" - ); - } - //add tags if does not exist - for (let i = 0; i < tagsAndDescription.length; i++) { - const tag = tagsAndDescription[i].tag; - const description = tagsAndDescription[i].description; - if (ghlabels.indexOf(tag) === -1) { - await CreateGHLabel(ghowner, ghrepo, tag, description); - } - } - } - - // init monitors - for (let i = 0; i < monitors.length; i++) { - const monitor = monitors[i]; - if (!fs.existsSync(monitor.path0Day)) { - fs.ensureFileSync(monitor.path0Day); - fs.writeFileSync(monitor.path0Day, JSON.stringify({})); - } - if (!fs.existsSync(monitor.path90Day)) { - fs.ensureFileSync(monitor.path90Day); - fs.writeFileSync(monitor.path90Day, JSON.stringify({})); - } - - console.log("Initial Fetch for ", monitor.name); - await Minuter(envSecrets, monitor, site.github); - await Ninety(monitor); - } - - //trigger minute cron - - for (let i = 0; i < monitors.length; i++) { - const monitor = monitors[i]; - - let cronExpession = "* * * * *"; - if (monitor.cron !== undefined && monitor.cron !== null) { - cronExpession = monitor.cron; - } - console.log("Staring " + cronExpession + " Cron for ", monitor.name); - Cron(cronExpession, async () => { - await Minuter(envSecrets, monitor, site.github); - }); - } - - //pre compute 90 day data at 1 minute interval - Cron( - "* * * * *", - async () => { - for (let i = 0; i < monitors.length; i++) { - const monitor = monitors[i]; - Ninety(monitor); - } - }, - { - protect: true - } - ); -}; - -export { Startup }; +Build(); diff --git a/config/monitors.example.yaml b/config/monitors.example.yaml index 219c783d6..f6819498e 100644 --- a/config/monitors.example.yaml +++ b/config/monitors.example.yaml @@ -1,17 +1,10 @@ -- name: Google Search - description: Search the world's information, including webpages, images, videos and more. - tag: "google-search" - image: "/google.png" +- name: OkBookmarks + description: A free bookmark manager that lets you save and search your bookmarks in the cloud. + tag: "okbookmarks" + image: "https://okbookmarks.com/assets/img/extension_icon128.png" api: method: GET - url: https://www.google.com/webhp -- name: Svelte Website - description: Cybernetically enhanced web apps - tag: "svelte-website" - api: - method: GET - url: https://svelte.dev/ - image: "/svelte.svg" + url: https://okbookmarks.com - name: Earth description: Our blue planet tag: "earth" diff --git a/config/site.example.yaml b/config/site.example.yaml index fa73fdcd2..fe5e0a623 100644 --- a/config/site.example.yaml +++ b/config/site.example.yaml @@ -1,10 +1,13 @@ -title: "Kener" +title: "Kener - Open-Source and Modern looking Node.js Status Page for Effortless Incident Management" +siteName: "Kener.ing" home: "/" logo: "/logo.png" +siteURL: "https://kener.ing" +favicon: "/logo96.png" github: owner: "rajnandan1" repo: "kener" - incidentSince: 48 + incidentSince: 720 metaTags: description: "Kener: Open-source modern looking 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. And the best part? Kener integrates seamlessly with GitHub, making incident management a team effort—making it easier for us to track and fix issues together in a collaborative and friendly environment." keywords: "Node.js status page, Incident management tool, Service monitoring, Service outage tracking, Real-time status updates, GitHub integration for incidents, Open-source status page, Node.js monitoring application, Service reliability, User-friendly incident management, Collaborative incident resolution, Seamless outage communication, Service disruption tracker, Real-time incident alerts, Node.js status reporting" @@ -21,11 +24,15 @@ metaTags: twitter:description: "Kener: 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. And the best part? Kener integrates seamlessly with GitHub, making incident management a team effort—making it easier for us to track and fix issues together in a collaborative and friendly environment." nav: - name: "Documentation" - url: "/docs" + url: "/docs/home" - name: "Github" + iconURL: "/github.svg" url: "https://github.com/rajnandan1/kener" + - name: "Buy me a coffee" + iconURL: "/buymeacoffee.svg" + url: "https://buymeacoffee.com/rajnandan1" hero: - title: Kener is a Open-Source Status Page System + title: Kener is a Modern Open-Source Status Page System subtitle: Let your users know what's going on. footerHTML: | Made using @@ -41,4 +48,8 @@ i18n: zh-CN: "中文" ja: "日本語" vi: "Tiếng Việt" -theme: dark +pattern: "squares" +analytics: + - id: "G-Q3MLRXCBFT" + type: "GA" + diff --git a/config/static/.kener b/config/static/.kener deleted file mode 100644 index 53d195994..000000000 --- a/config/static/.kener +++ /dev/null @@ -1 +0,0 @@ -I am Empty File \ No newline at end of file diff --git a/database/.kener b/database/.kener new file mode 100644 index 000000000..251d0a516 --- /dev/null +++ b/database/.kener @@ -0,0 +1 @@ +database folder \ No newline at end of file diff --git a/db/.kener b/db/.kener new file mode 100644 index 000000000..d0ddae9a0 --- /dev/null +++ b/db/.kener @@ -0,0 +1 @@ +.kener \ No newline at end of file diff --git a/dev.js b/dev.js deleted file mode 100644 index 20d9bdc3b..000000000 --- a/dev.js +++ /dev/null @@ -1,2 +0,0 @@ -import { Startup } from "./scripts/startup.js"; -Startup(); diff --git a/docker-compose.yml b/docker-compose.yml index 3628d4387..d33da656c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,13 +1,16 @@ version: '3.7' services: kener: - image: rajnandan1/kener:latest # assuming this is final namespace/image - container_name: kener + image: rajnandan1/kener:release-candidate-0.0.16 # assuming this is final namespace/image + container_name: kener-rc + #env_file: .env #uncomment this, if you are using .env file environment: - TZ=Etc/GMT #- GH_TOKEN= #- API_TOKEN= - #- API_IP + #- API_IP= + #- API_IP_REGEX= + #- KENER_BASE_PATH= # If running on a LINUX HOST and not podman rootless these MUST BE SET # run "id $user" from command line and replace numbers below with output from command @@ -17,15 +20,8 @@ services: ### Most likely DO NOT need to change anything below this ### #- PORT=3000 Port app listens on IN CONTAINER - - ### If any of the below are changed make sure the bound volume is correct as well ### - #- CONFIG_DIR=/config - #- PUBLIC_KENER_FOLDER=/config/static - #- MONITOR_YAML_PATH=/config/monitors.yaml - #- SITE_YAML_PATH=/config/site.yaml - #- KENER_BASE_PATH=/status - ports: - '3000:3000/tcp' volumes: - - '/host/path/to/config:/config:rw' + - './database:/app/database:rw' + - './config:/app/config' diff --git a/docker/root/etc/s6-overlay/s6-rc.d/svc-app/run b/docker/root/etc/s6-overlay/s6-rc.d/svc-app/run index 6a00a2096..ff7586f96 100755 --- a/docker/root/etc/s6-overlay/s6-rc.d/svc-app/run +++ b/docker/root/etc/s6-overlay/s6-rc.d/svc-app/run @@ -3,5 +3,12 @@ echo -e "\nApp is starting!" export NODE_ENV=production cd /app || exit -exec \ - s6-setuidgid abc /usr/bin/node $NODE_ARGS /app/prod.js + +# Run build first +s6-setuidgid abc /usr/bin/node $NODE_ARGS /app/build.js && ( + # Run startup and main in parallel + s6-setuidgid abc /usr/bin/node $NODE_ARGS /app/src/lib/server/startup.js & + s6-setuidgid abc /usr/bin/node $NODE_ARGS /app/main.js & + # Wait for both processes + wait +) \ No newline at end of file diff --git a/docs/categorize-guide.md b/docs/categorize-guide.md new file mode 100644 index 000000000..54c0b23ac --- /dev/null +++ b/docs/categorize-guide.md @@ -0,0 +1,45 @@ +--- +title: Categorize Monitors Guide | Kener +description: Categorize Monitors in Kener +--- + +# Categorize Monitors + +Let us add a category to our monitors. + +## Sample monitors.yaml + +```yaml +- name: OkBookmarks + description: A free bookmark manager that lets you save and search your bookmarks in the cloud. + tag: "okbookmarks" + image: "https://okbookmarks.com/assets/img/extension_icon128.png" + api: + method: GET + url: https://okbookmarks.com +- name: Earth + description: Our blue planet + tag: "earth" + defaultStatus: "UP" + image: "/earth.png" + category: "Hello" +- name: Frogment + description: A free openAPI spec editor and linter that breaks down your spec into fragments to make editing easier and more intuitive. Visit https://www.frogment.com + tag: "frogment" + image: "/frogment.png" + api: + method: GET + url: https://www.frogment.com +``` + +## Sample site.yaml + +```yaml +#... +categories: + - name: Hello + description: Say Hello to the world +#... +``` + +The above will have OkBookmarks and Frogment under home. Earth will be under Hello category. diff --git a/docs/changelogs.md b/docs/changelogs.md new file mode 100644 index 000000000..75543bda6 --- /dev/null +++ b/docs/changelogs.md @@ -0,0 +1,42 @@ +--- +title: Changelogs | Kener +description: Changelogs for Kener +--- + +# Changelogs + +## v0.0.16 + + + +Here are the changes in this release + +### Features + +- Added support for `hideURLForGet` in monitors. Read more [here](/docs/monitors) +- New SVG badges for LIVE status. Read more [here](/docs/status-badges#live) +- `[Breaking Change]` Removed dependency on Environment variable `PUBLIC_KENER_FOLDER`. Read more [here](#v0-0-16-migration) +- Simplified build and deploy process +- Added support for fonts. Read more [here](/docs/customize-site#font) +- Added support for home page pattern. Read more [here](/docs/customize-site#pattern) +- Added support for adding your analytics provider. Read more [here](/docs/site-analytics) +- New Documentation Site +- Addes support for `sqaures` pattern in home page. Read more [here](/docs/customize-site#pattern) +- Redesigned the UI for better consistency +- Embed now supports background color using a parameter `bgc`. Read more [here](/docs/embed#javascript-parameters) +- Now title in `site.yaml` is `+ +
+ + ++
@@ -151,7 +161,7 @@