Compare commits

..

526 Commits

Author SHA1 Message Date
Raj Nandan Sharma ca4f7040e8 refactor(themeInfo): remove maintenance status migration and update default value handling 2025-06-12 09:54:03 +05:30
Raj Nandan Sharma 1cfa861d7e Merge pull request #431 from rajnandan1/release/18
Release/18
2025-06-12 09:21:40 +05:30
Raj Nandan Sharma 007baefe80 Merge pull request #426 from ToxykAuBleu/feat/scheduled-downtime
Add maintenance visibility to monitor' status
2025-06-02 09:31:36 +05:30
Raj Nandan Sharma 7d77f4d996 chore: update version to 3.2.18 2025-05-28 10:01:15 +05:30
Raj Nandan Sharma 3d5a62c085 feat(subscription): add status toggle and restrict config editing #418
Adds support for toggling the subscription trigger's active/inactive status from the UI and updates backend/controller logic to allow dynamic status changes. Restricts editing of subscription event configuration and user subscriptions to when the trigger is active, improving admin control and preventing unintended changes when subscriptions are inactive.
2025-05-28 10:00:32 +05:30
Raj Nandan Sharma 24ffef997b feat(docker): copy server templates to build output #429 2025-05-28 08:52:37 +05:30
ToxykAuBleu 7d5275152c Fixing responsiveness of "Availability per Component", above monitors. 2025-05-26 12:08:03 +00:00
ToxykAuBleu 93167c2358 Reverting commit about recurring maintenance (forgotten). 2025-05-26 11:22:30 +00:00
ToxykAuBleu 734d3ad280 Adding translations for all translations. 2025-05-26 09:57:59 +00:00
ToxykAuBleu 753668dfa5 Adding documentation related to new maintenance status. 2025-05-26 06:58:03 +00:00
ToxykAuBleu 351817338d Reverting commits about recurring maintenance (moved to new branch). 2025-05-26 06:47:17 +00:00
ToxykAuBleu 7c12218489 Changing default color of maintenance' status. 2025-05-22 06:50:24 +00:00
ToxykAuBleu 7c71298227 Adding maintenance calculation & message to system data. 2025-05-22 06:41:10 +00:00
ToxykAuBleu 167db6d7e6 Adding visibility of this new status to Home page.
Adding MAINTENANCE to affected monitors (Events page).
2025-05-21 07:08:35 +00:00
Raj Nandan Sharma 74834f3559 Merge pull request #417 from chunchet-ng/main
fix: remove VOLUME declarations to prevent unintended anonymous volumes
2025-05-21 09:24:23 +05:30
ToxykAuBleu e13ca5c639 Adding visibility of this new type of maintenance into Events page. 2025-05-15 12:08:29 +00:00
Chun Chet a51806d62b fix: remove VOLUME declarations to prevent unintended anonymous volumes 2025-05-15 11:18:23 +08:00
ToxykAuBleu 57238dfb72 Adding input validation for maintenance duration.
Fixing some labels + flex layout.
2025-05-13 07:02:42 +00:00
ToxykAuBleu a2be642af0 Updating controller to accept new input data from Incident creation page.
Updating last migration to create new columns.
2025-05-13 07:01:14 +00:00
Raj Nandan Sharma aec2d23c2f Merge pull request #416 from OzkrOssa/feature/spanish-support
feat(i18n): add Spanish language support
2025-05-13 09:56:31 +05:30
Oscar Ossa 11a11162da feat(i18n): add Spanish language support 2025-05-12 09:48:06 -05:00
ToxykAuBleu 12a7be497e Editing incident creation page to include maintenance strategy option.
Removing unused properties inside newIncident.
2025-05-12 08:25:26 +00:00
github-actions 9b69da66a5 Auto-generate README.md with release versions 2025-05-06 17:27:39 +00:00
Raj Nandan Sharma ee831f96bb Merge pull request #413 from rajnandan1/css-fix-1
chore: update version to 3.2.17 and adjust layout properties in monit…
2025-05-06 22:40:06 +05:30
Raj Nandan Sharma 5542790145 chore: update version to 3.2.17 and adjust layout properties in monitor component 2025-05-06 22:39:09 +05:30
github-actions 3b1091a34f Auto-generate README.md with release versions 2025-05-06 01:54:32 +00:00
Raj Nandan Sharma eebe6541c3 Merge pull request #408 from ToxykAuBleu/fix/gamedig-call
Fixing missing call of any gamedig monitors.
2025-05-06 07:05:59 +05:30
Raj Nandan Sharma e308089cfa refactor(triggerInfo): remove timeout for test loaders and improve image tag formatting 2025-05-06 07:05:04 +05:30
Raj Nandan Sharma 5132d6894e Merge pull request #407 from ToxykAuBleu/fix/compiler-warnings
Fixing compiler warnings.
2025-05-06 07:02:19 +05:30
Raj Nandan Sharma 006d1e1fa0 Merge pull request #411 from rajnandan1/fix/ui-cycle
fix(notifications): improve error handling and UI feedback
2025-05-06 07:01:05 +05:30
Raj Nandan Sharma 4c6dc24355 fix(notifications): improve error handling and UI feedback
Returns structured error messages from notification providers, updates API logic to surface errors to users, and enhances UI to display error or success states during trigger tests. Also adjusts monitor API request payload and corrects data range calculation.

Relates to improved reliability and user experience.
2025-05-06 06:57:12 +05:30
ToxykAuBleu acf227a947 Fixing missing call of any gamedig monitors. 2025-05-05 09:57:39 +00:00
ToxykAuBleu 3b05ca73e5 Adding validation to this new status.
Adding migration + seed for this new status.
2025-05-05 09:05:16 +00:00
ToxykAuBleu 236d7ea585 Adding new maintenance status to theme page. 2025-05-05 09:04:37 +00:00
ToxykAuBleu 87408d22c4 Merge branch 'main' into fix/compiler-warnings 2025-05-05 06:54:14 +00:00
Raj Nandan Sharma ea6754775d feat: update localization files with new duration options for multiple languages 2025-05-05 11:16:38 +05:30
Raj Nandan Sharma 3210f4d406 Merge pull request #406 from rajnandan1/cycle-changer
feat: add configurable home data range per device
2025-05-05 10:50:15 +05:30
Raj Nandan Sharma 16ac97a9c9 feat: document hidden categories and configurable data range in changelogs 2025-05-05 10:48:08 +05:30
Raj Nandan Sharma 8857a51dde chore: update version to 3.2.16 in package.json 2025-05-05 10:44:23 +05:30
Raj Nandan Sharma fc2abe7f16 refactor: remove commented code and clean up dropdown labels in homePage and monitor components 2025-05-05 10:31:15 +05:30
Raj Nandan Sharma f04a930e13 feat: add configurable home data range per device
Introduces per-device configuration for maximum data range and selectable days on the homepage, allowing separate settings for desktop and mobile. Updates UI, server logic, and data model to support these options, and uses user agent detection to apply the correct configuration. Improves flexibility for data display across devices.

Relates to #105
2025-05-05 10:28:02 +05:30
Raj Nandan Sharma 1de1e2d6ba Merge pull request #405 from rajnandan1/hide-cate
refactor: rename visibility property to isHidden and update related l…
2025-05-04 12:47:32 +05:30
Raj Nandan Sharma 0208e4baf9 refactor: rename visibility property to isHidden and update related logic #321 2025-05-04 12:46:11 +05:30
Raj Nandan Sharma 21af2a6d02 Merge pull request #399 from rohith1222004/feature/monitorVisibility
feat: implement visibility control for monitors
2025-05-04 12:11:37 +05:30
github-actions 2fbe301a51 Auto-generate README.md with release versions 2025-04-30 17:53:33 +00:00
Raj Nandan Sharma 6d5382982c Merge pull request #402 from rajnandan1/fix0embed
feat(embed): add timezone support to embed parameters
2025-04-30 23:03:05 +05:30
Raj Nandan Sharma a20b4ee570 feat(embed): add timezone support to embed parameters
Introduces a 'tz' parameter for specifying timezone in embed scripts and iframe URLs, improving localization for embedded monitors.
Updates documentation and refactors parameter handling to prioritize explicit timezone over browser-detected values.

Relates to improved internationalization and user experience.
2025-04-30 23:02:10 +05:30
Raj Nandan Sharma 909ff9e070 Merge pull request #330 from Aj7ay7/main
docker/fix-compose-configuration
2025-04-30 19:58:05 +05:30
github-actions 7d3e265ece Auto-generate README.md with release versions 2025-04-30 14:18:34 +00:00
Raj Nandan Sharma 78d8f311c0 Merge pull request #400 from ToxykAuBleu/fix/398-docker-not-starting 2025-04-30 17:50:14 +05:30
ToxykAuBleu c52ba95fd0 Removing bad import of "@sveltejs/kit" in gamedigCall.js. 2025-04-30 11:37:43 +00:00
Rohith Kumar 9462a0331a feat: implement visibility control for monitors
- Private monitors are visible only to logged-in users
- Public monitors are accessible to all
2025-04-30 15:46:08 +05:30
ToxykAuBleu 4e3d1838c0 Fixing compiler warnings. 2025-04-30 06:43:21 +00:00
Raj Nandan Sharma aad7b5c6ea chore: update Dockerfile to use latest package versions for alpine build 2025-04-30 07:37:47 +05:30
Raj Nandan Sharma abec24d65b Merge pull request #397 from ToxykAuBleu/fix/396-gamedig-install
Updating Gamedig to 5.3.0 + fix build app.
2025-04-30 07:32:50 +05:30
Raj Nandan Sharma e0c3d1864c chore: remove specific version for iputils in Dockerfile 2025-04-30 07:14:54 +05:30
ToxykAuBleu bd25c6fe05 Updating Gamedig to 5.3.0.
Moving Gamedig to dependencies (not devDependencies).
2025-04-29 20:34:13 +00:00
Raj Nandan Sharma 4ec5e14431 chore: update changelog for version 3.2.14 with new features and bug fixes 2025-04-29 22:58:09 +05:30
Raj Nandan Sharma c9b8c5bec3 chore: update changelogs for version 3.2.14 with new features and bug fixes 2025-04-29 22:50:28 +05:30
Raj Nandan Sharma 223883adf7 Merge pull request #385 from rajnandan1/event-subscription
Event subscription
2025-04-29 22:21:22 +05:30
Raj Nandan Sharma 83c2d7ff79 fix: improve subscription UX and email code handling
Enhances subscription menu by showing a default icon when no image is available.
Replaces hardcoded verification code in email template with a variable.
Prevents background scrolling when subscription menu is open.
Removes redundant new subscriber response in API.
Removes unused documentation button from subscriptions page.
Adjusts checkbox styling for better UI consistency.
2025-04-29 22:07:39 +05:30
Raj Nandan Sharma cf33f5a2f6 refactor: improve downtime duration calculation in closure comment #387 2025-04-29 21:20:04 +05:30
Raj Nandan Sharma 92b4596924 refactor: centralize monitoring data insertion logic
Introduces a unified function for monitoring data insertion,
adds support for group monitor status updates, and ensures
consistency by routing all monitoring data writes through the
new logic. Removes duplicated validation and streamlines
group monitor handling for improved reliability.

Relates to #123
2025-04-29 20:45:14 +05:30
Raj Nandan Sharma ce9b748570 style: adjust height of checkbox and refine status color banner with hover effects 2025-04-28 22:57:51 +05:30
Raj Nandan Sharma 87a9069ab4 feat: add configurable site status banner with i18n
Introduces an option to enable a site status banner summarizing the operational state of monitored systems. Calculates and displays aggregated status with a progress bar. Localizes banner messages in all supported languages.

Helps users quickly assess overall system health from the homepage.
2025-04-28 22:18:15 +05:30
Raj Nandan Sharma 3e6a76c8c3 Refactor monitoring data update logic to include type parameter and optimize database insertion with batching 2025-04-28 10:48:33 +05:30
Raj Nandan Sharma 3509955374 Implement date input for monitoring data modification and enhance validation logic 2025-04-28 10:08:58 +05:30
Raj Nandan Sharma 225711f95d Merge branch 'main' into event-subscription 2025-04-27 16:10:35 +05:30
Raj Nandan Sharma 5436647cd3 Merge pull request #380 from mcorbin/feature/allow-modifying-monitors-data
Allow users to modify monitors data
2025-04-27 16:10:22 +05:30
Raj Nandan Sharma fbd9da3b33 Merge branch 'main' into feature/allow-modifying-monitors-data 2025-04-27 16:07:31 +05:30
Raj Nandan Sharma edb6705ac8 Merge branch 'main' into event-subscription 2025-04-27 15:18:47 +05:30
Raj Nandan Sharma 382a2a5bcc Merge pull request #384 from jensvandenreyt/feature/extend-monitor-api
feat: add CRUD api for monitors
2025-04-27 15:18:29 +05:30
Raj Nandan Sharma b5967607d2 Refactor game data handling and improve metadata for event pages
- Updated monitorSheet.svelte to use raw JSON data for AllGamesList, replacing the previous import method.
- Refactored game retrieval logic in monitorSheet.svelte to utilize GetGameFromId function with the new AllGamesList structure.
- Modified monitorsAdd.svelte to parse AllGamesList from raw JSON, ensuring consistent data usage across components.
- Enhanced event pages (+page.svelte) with appropriate meta tags for better SEO, including titles and descriptions.
- Added a new all-games-list.json file containing comprehensive game data for improved functionality and maintainability.
- Updated subscriptions page to clarify SMTP setup instructions for users.
2025-04-27 15:05:35 +05:30
Raj Nandan Sharma 03bbb6c3b6 Merge branch 'main' into event-subscription 2025-04-27 12:57:17 +05:30
Raj Nandan Sharma 6054be0ff1 feat: enhance subscription table with index for optimized queries 2025-04-27 12:56:31 +05:30
Raj Nandan Sharma ff7355bcc2 Merge pull request #389 from ToxykAuBleu/feat/gamedig
Adding Gamedig monitor functionality and related documentation
2025-04-27 12:55:52 +05:30
Raj Nandan Sharma 23a9124e03 feat(i18n): localizes subscription and maintenance UI
Integrates translation function into subscription and maintenance components,
localizing all user-facing strings. Expands translation resources for multiple languages
to cover newly localized phrases, improving accessibility and user experience for non-English users.
2025-04-27 12:12:10 +05:30
Raj Nandan Sharma ac41807cdd Merge pull request #392 from myned/fix-smtp-secure
Fix SMTP_SECURE environment variable always being truthy when set
2025-04-27 11:08:36 +05:30
Raj Nandan Sharma d95bb13e6e refactor: unify incident URL paths and add incident pagination
Unifies URLs for incident events by switching from singular to plural path segments and removes obsolete routes to streamline navigation. Introduces paginated incident fetching with filtering and sorting to improve performance and scalability. Updates UI components to reflect new paths and behaviors.

Relates to improved incident management and navigation.
2025-04-27 10:13:00 +05:30
Raj Nandan Sharma b9d58ba3c1 remove debug console log from +page.svelte 2025-04-26 14:01:06 +05:30
Raj Nandan Sharma a648b29f31 feat: add incident link copy button and chevron hide option
Introduces a copy-to-clipboard button for incident links with animated feedback. Adds ability to hide accordion chevrons and refactors chevron and copy button styles. Updates incident URL generation and allows querying monitors by tag.

Relates to improved UX for incident management.
2025-04-26 14:00:23 +05:30
Myned e702feb04f fix: coerce SMTP_SECURE to Number before boolean 2025-04-26 00:18:51 -05:00
Raj Nandan Sharma ce83355e58 feat: add incident event email notifications via queue
Introduces a queue-based system to send email notifications for incident events, including creation, updates, monitor changes, and comments. Fetches eligible subscribers dynamically and uses configurable templates for email delivery. Also updates the UI to improve the Preview button styling for better user experience.
2025-04-25 20:38:59 +05:30
ToxykAuBleu 683beed3b7 Refactoring port validation into a function: IsValidPort(port). 2025-04-24 07:01:37 +00:00
ToxykAuBleu c9095036eb Fixing auto formatting done in #e63992a. 2025-04-24 06:36:36 +00:00
Jens Vandenreyt 29ec935d67 feat: update json OpenAPI spec
Also improve 400 and 404 error messages
2025-04-23 16:17:53 +02:00
ToxykAuBleu 339015a093 Adding documentation related to Gamedig monitor. 2025-04-23 11:49:48 +00:00
Jens Vandenreyt b8b5b4170f Merge branch 'rajnandan1:main' into feature/extend-monitor-api 2025-04-23 11:59:21 +02:00
ToxykAuBleu 8a104e4480 Adding timeout feature for Gamedig monitor. 2025-04-23 06:31:41 +00:00
Raj Nandan Sharma 89e1a5e88a Merge branch 'main' into event-subscription 2025-04-23 11:31:58 +05:30
Raj Nandan Sharma 8e5746d068 style: Update logout button design and improve subscription info display 2025-04-23 11:31:40 +05:30
Raj Nandan Sharma 1df7c6560a Merge pull request #386 from lolen/main
feat: Polish translation
2025-04-23 11:24:38 +05:30
Piotr Lasota 0ff4546379 feat: Polish translation 2025-04-22 22:56:13 +02:00
Raj Nandan Sharma ab036c968d Refactor subscription trigger configuration and update database schema for subscription management 2025-04-22 22:58:45 +05:30
Raj Nandan Sharma 625ece2e7b feat: Add token generation with expiry and enhance subscription management
- Implemented GenerateTokenWithExpiry function for JWT token generation with a specified expiry time.
- Added GetSubscriberByID function to retrieve subscriber details by ID.
- Updated CreateNewSubscription to accept subscriber ID directly.
- Introduced GetSubscribersPaginated for paginated retrieval of subscribers.
- Enhanced subscription trigger management with CreateSubscriptionTrigger and GetSubscriptionTriggerByEmail functions.
- Updated email_code.html to change expiration notice from 1 day to 5 minutes.
- Modified layout.server.js to include canSendEmail check for conditional rendering.
- Updated subscription page to display subscribers with pagination and subscription status management.
- Refactored subscription-related API endpoints for improved clarity and functionality.
- Added server-side logic for managing subscription triggers and email settings.
2025-04-22 21:30:17 +05:30
Jens Vandenreyt 41842cc1b9 feat: add CRUD api for monitors
Added API endpoints for search, get, create, update and delete of monitors
2025-04-22 16:07:54 +02:00
ToxykAuBleu e63992a7a5 Adding eval feature for Gamedig monitor.
Sorting by default games by name.
2025-04-22 11:54:07 +00:00
ToxykAuBleu 7ae5d8f1b1 Adding Gamedig monitor functionality. 2025-04-19 19:13:40 +00:00
Raj Nandan Sharma 0a34f2138a feat: Implement subscriptions page and update navigation 2025-04-19 13:56:31 +05:30
Raj Nandan Sharma 38b326fe72 feat: Implement subscription management features
- Added functionality to create, update, and delete subscribers and their subscriptions in the controller and database implementation.
- Introduced new API endpoints for subscribing and unsubscribing users, including email verification and management of subscription preferences.
- Created a new Svelte component for managing subscriptions, allowing users to subscribe to updates and manage their preferences.
- Added email template for sending verification codes to subscribers.
- Implemented utility functions for generating random numbers and validating email addresses.
2025-04-19 13:09:51 +05:30
Mathieu Corbin 8785c609a9 Allow users to modify monitors data
Sometimes, it's useful to modify data in the monitoring_data table, for example, in case of a false positive.

I'm adding a new button in the monitor list that opens a popup
allowing users to change the status of monitor data for a given time
range.
2025-04-17 15:13:44 +02:00
github-actions 1a67560137 Auto-generate README.md with release versions 2025-04-08 05:33:49 +00:00
Raj Nandan Sharma 6c7534b9b6 Merge pull request #374 from rajnandan1/release/3.2.13
Release/3.2.13
2025-04-08 10:46:07 +05:30
Raj Nandan Sharma ae6c2c985f feat: enhance incident details formatting and add active status filter for triggers #370 2025-04-08 10:44:17 +05:30
Raj Nandan Sharma 940331b87f feat: update package description and enhance incident date formatting #371 2025-04-08 09:23:12 +05:30
Raj Nandan Sharma e6b5600a47 feat: add Umami analytics support and enhance capture functionality 2025-04-02 21:16:34 +05:30
Raj Nandan Sharma ce2a6ab756 feat: update version to 3.2.13 and refactor monitoring data calculations 2025-04-01 22:51:55 +05:30
Raj Nandan Sharma 6af91af639 chore: bump version to 3.2.12 in package.json 2025-04-01 21:22:43 +05:30
github-actions e1ff156b91 Auto-generate README.md with release versions 2025-03-31 12:16:53 +00:00
Raj Nandan Sharma b56d20ac35 Merge pull request #367 from rajnandan1/release/3.2.12
feat: add kenerTheme support and enhance footer HTML structure
2025-03-31 17:17:30 +05:30
Raj Nandan Sharma 9ee7a7b861 feat: add kenerTheme support and enhance footer HTML structure 2025-03-31 17:12:40 +05:30
Raj Nandan Sharma f73ffce3f0 feat: enhance documentation and styling for custom JS/CSS guide and incident comments 2025-03-30 12:58:19 +05:30
Raj Nandan Sharma 0997967787 docs: update environment variables documentation for CORS and reverse proxy setup
feat: modify analytics event naming convention for consistency

fix: adjust delete monitor confirmation form layout for better UX

style: refine positioning of monitor action buttons for improved alignment

refactor: streamline password validation imports in setup and forgot password routes

fix: remove unnecessary line breaks in layout server file
2025-03-29 22:33:17 +05:30
github-actions 3476ec2b29 Auto-generate README.md with release versions 2025-03-28 16:39:18 +00:00
Raj Nandan Sharma e0cf568aa9 feat: implement custom handle function to modify response headers 2025-03-28 21:50:07 +05:30
Raj Nandan Sharma c12d7f8345 feat: add null check for document before dispatching analytics event 2025-03-28 21:18:26 +05:30
Raj Nandan Sharma 9ba115d5ab Merge pull request #366 from rajnandan1/release/3.2.11
release 3.2.11
2025-03-28 20:56:59 +05:30
Raj Nandan Sharma 3756bbfd06 chore: bump version to 3.2.11 in package.json 2025-03-28 20:54:50 +05:30
Raj Nandan Sharma 9096bcc641 feat: import analytics event module for enhanced tracking in incident page 2025-03-28 20:53:44 +05:30
Raj Nandan Sharma c4d04854e5 feat: enhance monitor deletion confirmation UI and add analytics event for incident back button click 2025-03-28 20:52:59 +05:30
Raj Nandan Sharma d95fc0005b feat: indicate Markdown support in incident summary and comment sections 2025-03-28 20:40:24 +05:30
Raj Nandan Sharma 2b9093184a feat: add support for Markdown language in events and enhance SEO documentation as requested in #359 2025-03-28 20:34:19 +05:30
Raj Nandan Sharma 831376054b feat: add analytics capture snippets and enhance event tracking also added plausible and ms clarity #355 2025-03-28 15:07:45 +05:30
Raj Nandan Sharma 885ee0b6d6 feat: implement monitor deletion functionality with confirmation 2025-03-26 11:47:53 +05:30
Raj Nandan Sharma 511d0f26dd feat: add email validation in CreateUpdateTrigger function 2025-03-26 10:16:36 +05:30
github-actions 5263698625 Auto-generate README.md with release versions 2025-03-25 17:45:05 +00:00
Raj Nandan Sharma 7859f9564e fix: bump version to 3.2.10 in package.json 2025-03-25 22:58:04 +05:30
github-actions 149456682d Auto-generate README.md with release versions 2025-03-25 17:20:55 +00:00
Raj Nandan Sharma 8c040997da fix: refactor monitorTriggers initialization into a separate function 2025-03-25 22:47:05 +05:30
Raj Nandan Sharma 868fed0176 fix: update tzdata version in Dockerfile to use latest available 2025-03-25 22:38:13 +05:30
Raj Nandan Sharma c7f44a48da fix: bump version to 3.2.9 in package.json 2025-03-25 22:31:57 +05:30
Raj Nandan Sharma 6300bff4e7 Merge pull request #363 from rajnandan1/fix/357
fix: update RBAC documentation and improve monitor data retrieval log…
2025-03-25 22:27:04 +05:30
Raj Nandan Sharma 3e6b1ffdbb fix: streamline query execution in DbImpl by removing redundant return statement 2025-03-25 22:24:10 +05:30
Raj Nandan Sharma 408fd5318b fix: update RBAC documentation and improve monitor data retrieval logic, fixes #357 and #361 2025-03-25 22:20:45 +05:30
Raj Nandan Sharma cfe419f6df Merge pull request #362 from rajnandan1/fix/360
Fix/360
2025-03-25 20:39:07 +05:30
Raj Nandan Sharma 5403726099 fix: replace parseInt with Number for timeout conversion in monitorSheet.svelte and ping.js 2025-03-25 20:38:23 +05:30
Raj Nandan Sharma 057c14909f fix #360 2025-03-25 20:29:30 +05:30
Raj Nandan Sharma 37b18c06bb fix: validate timeout input and ensure it's a number in monitorSheet.svelte 2025-03-25 20:28:39 +05:30
github-actions 3ad1eb9a03 Auto-generate README.md with release versions 2025-03-24 15:18:34 +00:00
Raj Nandan Sharma a1064771f8 fix: increment package version from 3.2.7 to 3.2.8 in package.json 2025-03-24 20:34:24 +05:30
Raj Nandan Sharma 463ee286bf fix: increment package version from 3.2.6 to 3.2.7 in package.json 2025-03-24 20:30:05 +05:30
Raj Nandan Sharma a7ae6e72ef fix: downgrade package version from 3.2.8 to 3.2.6 in package.json 2025-03-24 20:28:01 +05:30
github-actions 36c67f60dd Auto-generate README.md with release versions 2025-03-24 14:56:20 +00:00
Raj Nandan Sharma bb718b04a6 fix: update Dockerfile to use ARG variables for Node.js version tags 2025-03-24 20:10:13 +05:30
Raj Nandan Sharma b6ea060054 fix: update publish-images workflow and increment package version to 3.2.8 2025-03-24 20:01:53 +05:30
Raj Nandan Sharma 5dba54c048 fix: update publish-images workflow to set GH_TOKEN environment variable correctly 2025-03-24 19:53:29 +05:30
Raj Nandan Sharma 415276163f Merge pull request #356 from rajnandan1/release/3.2.8-x
fix: update Node.js version to 23 and increment package version to 3.2.7
2025-03-24 19:45:44 +05:30
Raj Nandan Sharma 017b9e2e84 fix: update Node.js version to 23 and increment package version to 3.2.7 2025-03-24 19:45:15 +05:30
Raj Nandan Sharma 5740991ee1 fix: update GitHub Actions workflow to use GH_PAT instead of GITHUB_TOKEN for authentication 2025-03-24 15:12:40 +05:30
Raj Nandan Sharma 06008bd719 fix: streamline Dockerfile by removing unnecessary paths and using variable references for base images 2025-03-24 12:11:03 +05:30
Raj Nandan Sharma b53cd485e5 Merge pull request #263 from kaffolder7/feature/dependabot-version-updates
feat: automate dependency updates 🤖
2025-03-24 11:59:41 +05:30
Raj Nandan Sharma 7c1fd39158 Merge branch 'main' into feature/dependabot-version-updates 2025-03-24 11:58:58 +05:30
Raj Nandan Sharma f7888f1ba4 fix: update package versions in Dockerfile for consistency and stability 2025-03-24 11:32:05 +05:30
Raj Nandan Sharma ece5ac37ad feat: update embed monitor URLs to use siteURL and base variables for improved flexibility 2025-03-24 11:19:51 +05:30
Raj Nandan Sharma a51183fb23 Merge pull request #348 from rajnandan1/rbac
Features role-based access control and user management
2025-03-24 10:45:39 +05:30
Raj Nandan Sharma 7875192fb0 feat: update package dependencies and improve invitation expiry date formatting 2025-03-24 10:17:21 +05:30
Raj Nandan Sharma eb128ad431 feat: add "View in detail" button for group monitors and update localization files 2025-03-23 22:44:11 +05:30
Raj Nandan Sharma ba61ed87ab feat: enhance incident month handling with improved date calculations and formatting, fixing #349 2025-03-23 21:51:51 +05:30
Raj Nandan Sharma 8b5348081d docs: update changelogs to include improvements in build time and component migration 2025-03-23 14:29:15 +05:30
Raj Nandan Sharma bb7e69ace5 fix: update icon imports to use specific paths from lucide-svelte 2025-03-23 14:28:04 +05:30
Raj Nandan Sharma 3b8bb60423 fix: update print width in configuration files and improve documentation links 2025-03-23 13:36:22 +05:30
Raj Nandan Sharma 93339ce9df feat: add support for self-signed certificates and update changelog, fixes #351 2025-03-23 12:32:36 +05:30
Raj Nandan Sharma d30d99d8c2 docs: update changelogs to include resolved GitHub issues and improve formatting 2025-03-22 22:57:26 +05:30
Raj Nandan Sharma 3468df16e1 fix: improve error handling and code structure in Minuter function #345 2025-03-22 22:50:17 +05:30
Raj Nandan Sharma ccdeff98dc feat: implement dynamic versioning in headers and documentation as reported in #346 2025-03-22 21:54:24 +05:30
Raj Nandan Sharma 7e182b1df2 fix: replace showModal with closeModal for better modal handling #350 2025-03-22 21:06:37 +05:30
Raj Nandan Sharma ff652ad435 fix: #238 #210 #309 #334 2025-03-22 16:42:21 +05:30
Raj Nandan Sharma 1fdafa7966 Adds badges and embed features
Implements badges for monitors, including status, uptime, and liveness, along with a dedicated management page.

Adds embed options for various platforms with customizable styles.
2025-03-22 16:36:19 +05:30
Raj Nandan Sharma 1f5e683af5 feat: integrate CodeMirror editor for enhanced JavaScript and JSON editing experience 2025-03-20 23:23:54 +05:30
Raj Nandan Sharma db8c881571 refactor: remove performance logging and clean up code in various files 2025-03-19 19:45:12 +05:30
Raj Nandan Sharma 14f49c5b3b Features role-based access control and user management
Implements role-based access control with admin, editor, and member roles.

Introduces a user management system with profiles, activation/deactivation, and password reset.

Adds an email authentication system with verification and password reset via email.

Includes an invitation system with token-based invitations and admin controls.

Improves performance, security, UI, and developer experience.

Updates package version to 3.2.5 and adds vite-plugin-package-version.

Removes libcap related code from Dockerfile.
2025-03-18 23:08:51 +05:30
Raj Nandan Sharma 52286f26a5 fix: update version to 3.2.5 and reflect changes in User-Agent and documentation 2025-03-13 09:55:04 +05:30
github-actions 45e7567cb6 Auto-generate README.md with release versions 2025-03-12 17:48:41 +00:00
Raj Nandan Sharma c0edb59884 chore: update version to 3.2.4 and reflect changes in User-Agent and documentation 2025-03-12 23:16:48 +05:30
Raj Nandan Sharma 1e86c429ca fix: install libcap tools for setting capabilities in Dockerfile 2025-03-12 23:00:18 +05:30
Raj Nandan Sharma fbaebdcbcf Merge pull request #344 from rajnandan1/fix-ping-non-root
refactor: clean up Dockerfile and improve security practices
2025-03-12 22:49:47 +05:30
Raj Nandan Sharma 3f9716b0a4 fix: update donation links from GitHub Sponsors to Buy Me a Coffee 2025-03-12 22:48:53 +05:30
Raj Nandan Sharma 57d32197cf chore: update version to 3.2.3 and reflect changes in documentation and headers 2025-03-12 22:05:25 +05:30
Raj Nandan Sharma 9e6a3f26c2 refactor: clean up Dockerfile and improve security practices 2025-03-12 21:30:06 +05:30
github-actions 23314beb15 Auto-generate README.md with release versions 2025-03-08 18:58:59 +00:00
Raj Nandan Sharma 64b94da585 Merge pull request #340 from rajnandan1/release/3.2.2
Release/3.2.2
2025-03-08 22:57:28 +05:30
Raj Nandan Sharma 1609b4fc50 refactor: remove unnecessary future and ongoing incident checks from IncidentNew component 2025-03-08 22:56:57 +05:30
Raj Nandan Sharma dfffffb101 fix: remove database directory writable check from entrypoint script 2025-03-08 22:26:14 +05:30
Raj Nandan Sharma 81250a117a chore: update version to 3.2.2 in package.json and documentation 2025-03-08 22:18:53 +05:30
Raj Nandan Sharma 72bd0241e9 fix: streamline incident creation logic for database compatibility , fixes #325 2025-03-08 22:17:48 +05:30
Raj Nandan Sharma 63c24f147f fix: remove redundant startup command in entrypoint script #338 2025-03-08 21:19:46 +05:30
Raj Nandan Sharma 88fb7df3f5 Enhances incident display with time status
Improves the incident display by adding time status information
such as "Starts in", "Started", and "Will last for".
Also fixes database directory write permissions on startup.

Also fixes #337
2025-03-08 21:17:34 +05:30
Raj Nandan Sharma 320b1a0cc5 Improves webhook and notification handling
- Makes the monitor tags wrap on smaller screens.
- Validates webhook body.
- Adds user agent to webhook.
- Fixes Discord logo URL construction.

Issue #336
2025-03-07 20:09:35 +05:30
Raj Nandan Sharma 08b168ee6e Merge pull request #331 from rajnandan1/feature/hb1
Adds heartbeat monitor and improvements
2025-03-01 21:56:21 +05:30
Raj Nandan Sharma d6a87ac81a docs: update heartbeat monitors description for clarity
refactor: remove unused RandomString import from monitorSheet component

chore: clean up commented-out code in FetchData function
2025-03-01 21:55:37 +05:30
Raj Nandan Sharma 615dba42b8 fix: change default monitor type from HEARTBEAT to NONE 2025-03-01 21:02:12 +05:30
Raj Nandan Sharma 3ffec4f1fe Adds heartbeat monitor and improvements
Implements push-based monitoring via heartbeats.

Fixes data interpolation issues.

Enhances UI and documentation.
2025-03-01 21:00:05 +05:30
Aj7ay7 1288b463f1 Aj7ay7/fix/docker-compose-config
hotfix: updated container_name in compose
2025-02-28 23:21:02 +05:30
Aj7ay7 1aed6d0703 hotfix: updated container_name in compose 2025-02-28 23:20:38 +05:30
Aj7ay7 69f1e69140 Merge pull request #2 from Aj7ay7/revert-1-fix/docker-compose-config-validation
Revert "hotfix: Docker Compose Configuration Issues"
2025-02-28 23:17:52 +05:30
Aj7ay7 ab4ecd29c6 Revert "hotfix: Docker Compose Configuration Issues" 2025-02-28 23:17:40 +05:30
Aj7ay7 1d76c8a3d3 fix: docker-compose-config-validation
hotfix: Docker Compose Configuration Issues
2025-02-28 23:13:49 +05:30
Aj7ay7 b1c2cedbac hotfix: Docker Compose Configuration Issues 2025-02-28 23:11:13 +05:30
Raj Nandan Sharma 60b4b6f207 Merge pull request #327 from rajnandan1/feature/hb
feat: enhance documentation and add donation banner for Kener
2025-02-28 10:00:37 +05:30
Raj Nandan Sharma b9f5eb56c5 feat: enhance documentation and add donation banner for Kener 2025-02-28 09:59:39 +05:30
github-actions 7b9ae73044 Auto-generate README.md with release versions 2025-02-27 05:47:27 +00:00
Raj Nandan Sharma 87f2c33eba Merge pull request #323 from rajnandan1/feature/optimize-1
feat: upgrade to version 3.2.0 with improved monitor evaluation funct…
2025-02-27 10:54:40 +05:30
Raj Nandan Sharma 0a73a8b10a fix: update eval function to use responseRaw instead of responseData 2025-02-27 10:53:23 +05:30
Raj Nandan Sharma af65404fd3 feat: upgrade to version 3.2.0 with improved monitor evaluation functions and enhanced API support 2025-02-27 10:46:59 +05:30
github-actions 398f891dac Auto-generate README.md with release versions 2025-02-27 01:19:17 +00:00
Raj Nandan Sharma 6a2375a774 Merge pull request #322 from rajnandan1/fix/optimize
fix: eval not working for api
2025-02-27 06:30:30 +05:30
Raj Nandan Sharma 43bbcf4015 fix: eval not working for api 2025-02-27 06:29:39 +05:30
github-actions fadb563337 Auto-generate README.md with release versions 2025-02-26 07:02:13 +00:00
Raj Nandan Sharma 1ae54b3906 Improve formatting and add video tutorial section in quick start documentation 2025-02-26 12:20:17 +05:30
Raj Nandan Sharma 1c069e2ee2 Bump version to 3.1.9 and update documentation layout 2025-02-26 12:12:17 +05:30
Raj Nandan Sharma ed1099ac11 Merge pull request #319 from rajnandan1/feature/sql-monitor
Adds SQL monitor functionality as asked in #244
2025-02-26 12:09:39 +05:30
Raj Nandan Sharma 99d3a7e046 Adds SQL monitor functionality as asked in #244
Implements the SQL monitor feature, allowing users to monitor database connections and queries.

Adds UI elements for configuring SQL monitor parameters, including connection string, query, and timeout.

Validates user inputs for SQL monitor configuration.
2025-02-26 12:06:05 +05:30
Raj Nandan Sharma 2144acf34d Merge pull request #318 from rajnandan1/feature/ssl
Add SSL monitor functionality and related documentation #317
2025-02-26 09:15:46 +05:30
Raj Nandan Sharma 7a8ad8e833 Enhance port validation logic in SSL configuration for improved error handling 2025-02-26 09:14:04 +05:30
Raj Nandan Sharma 3b45f33692 Remove unnecessary assignment of type_data in monitorSheet component 2025-02-26 09:11:26 +05:30
Raj Nandan Sharma b4a2340ec7 Add SSL monitor functionality and related documentation #317 2025-02-26 09:09:20 +05:30
Raj Nandan Sharma 5910e3b930 Merge pull request #315 from rajnandan1/feature/css-cls
Refactor section classes for improved styling and organization in the…
2025-02-24 22:05:54 +05:30
Raj Nandan Sharma fd58beaa69 Refactor section classes for improved clarity and organization in incident page layout 2025-02-24 22:05:16 +05:30
Raj Nandan Sharma 5449e422a5 Refactor section classes for improved styling and organization in the hero and event sections 2025-02-24 22:02:53 +05:30
Raj Nandan Sharma dfb1784b6f Merge pull request #312 from rajnandan1/feature/cheerio
Add cheerio dependency and enhance API call examples with HTML parsing
2025-02-24 07:01:24 +05:30
Raj Nandan Sharma d956c60b48 Add cheerio dependency and enhance API call examples with HTML parsing 2025-02-24 06:56:09 +05:30
github-actions 69e1f2af6e Auto-generate README.md with release versions 2025-02-23 12:41:00 +00:00
Raj Nandan Sharma 7cc2f5ed4b Refactor i18n documentation for improved clarity and consistency in localization instructions 2025-02-23 17:46:54 +05:30
Raj Nandan Sharma ce2b17a5c9 Merge pull request #311 from rajnandan1/fix/double-trigger
Refactors incident handling and cron scheduling
2025-02-23 17:45:35 +05:30
Raj Nandan Sharma 0fcc60bf65 Bump version to 3.1.8 and update changelog with new features, improvements, and fixes 2025-02-23 17:39:26 +05:30
Raj Nandan Sharma 45ac25055b Enhances internationalization support by adding localized timezone messages and updating UI text for language consistency 2025-02-23 16:55:44 +05:30
Raj Nandan Sharma d3e201f2e4 Adds timezone support and UI toggle
Improves date formatting by adding timezone support using `date-fns-tz`.

Allows users to switch between different timezones via a new UI toggle in the settings.
Updates dependencies and integrates timezone functionality into date formatting functions.
2025-02-23 16:35:41 +05:30
Raj Nandan Sharma 693735dc2e Improves monitor component and incident handling
Refactors the monitor component for better data display and user interaction, including improved uptime calculations and a dropdown for selecting time ranges.

Enhances incident creation and handling by adding incident sources and refining incident filtering.

Addresses UI responsiveness on smaller screens.
2025-02-22 23:00:33 +05:30
Raj Nandan Sharma a4fa85dd79 Refactors incident handling and cron scheduling
Improves incident management by filtering out existing auto incidents when creating manual incidents.

Enhances cron job scheduling by removing and adding jobs dynamically based on active monitors and prevents duplicated incidents.
Also, ensures jobs get triggered in the correct order.
2025-02-22 11:48:39 +05:30
github-actions 29f70860ef Auto-generate README.md with release versions 2025-02-20 03:17:31 +00:00
Kyle ae882f6afc Merge pull request #305 from kaffolder7/fix/docker-image-tagging
fix: docker gha build
2025-02-19 19:19:57 -05:00
Kyle 57941f67ce Merge branch 'rajnandan1:main' into fix/docker-image-tagging 2025-02-19 19:19:10 -05:00
Kyle Affolder ab356d516b fix(docker): gha build
Pulled version number from release rather than via tag (since this workflow is triggered by release).
2025-02-19 19:17:56 -05:00
Kyle Affolder d11b3f1a99 fix(docker): gha build 2025-02-19 19:15:14 -05:00
Kyle 83ba08dd6b Merge pull request #304 from kaffolder7/fix/docker-image-tagging
fix(docker): clean up Docker tagging
2025-02-19 18:41:08 -05:00
Kyle Affolder 34cc00a1f6 fix(docker): clean up Docker tagging
Simplified Docker tagging - in turn fixes broken `alpine` tag to correctly point to latest stable Alpine release. Changes include:

- No more type=ref,event=branch – because this workflow is only for releases & manual triggers on `main` branch.
- Ensures `alpine` tag is always created for Alpine variant builds.
- Ensures `latest` tag is always created for Debian builds.
- Ensures all semver-based tags work correctly for both variants.
2025-02-19 18:39:34 -05:00
github-actions 7b86429056 Auto-generate README.md with release versions 2025-02-19 22:50:32 +00:00
Kyle e67c1b1539 Merge pull request #303 from kaffolder7/fix/docker-image-tagging
debug(docker): tagging issue
2025-02-19 17:36:20 -05:00
Kyle Affolder e501f8e05d debug(docker): tagging issue
Debugging `alpine` tag as it does not seem to be pointing to the most recent stable release.
2025-02-19 17:35:46 -05:00
github-actions 9e40d89317 Auto-generate README.md with release versions 2025-02-19 04:58:24 +00:00
Raj Nandan Sharma 5d475ab23f Merge pull request #302 from rajnandan1/fix/incidents-1
Fix/incidents 1
2025-02-19 10:12:08 +05:30
Raj Nandan Sharma 01cb39e18e Bump version to 3.1.7 and update favicon type label; improve incident description formatting 2025-02-19 10:10:31 +05:30
Raj Nandan Sharma 0d0ef25970 Improve documentation for SMTP configuration and add external link button in monitors management 2025-02-19 09:52:09 +05:30
Raj Nandan Sharma 6c766e2001 Enhances incident management and SMTP configuration
Improves incident display and management by introducing configurable incident group views and enhancing comment rendering to support HTML content.

Solves the bug raised in #295 where server crashes when an incident is created from an alert

Refines SMTP email settings by adding TLS configuration and allowing username/password to be optional. #300 and #298

Also, fixes a bug where only home page was being filtered. Now all pages are filtered. #297
2025-02-19 07:05:24 +05:30
Raj Nandan Sharma 1c73166120 Merge pull request #292 from kaffolder7/feature/embed-webfont
feat: embed project webfont
2025-02-18 07:46:56 +05:30
github-actions fc5dbc85dc Auto-generate README.md with release versions 2025-02-18 02:06:31 +00:00
Raj Nandan Sharma 622327cdf6 Merge pull request #294 from rajnandan1/fix/293-and-287
Bump version to 3.1.6 and update affected status handling in database…
2025-02-18 07:22:13 +05:30
Raj Nandan Sharma b0a4cd5c42 Bump version to 3.1.6 and update affected status handling in database queries for pg db reported in #293 and #287 2025-02-18 07:20:08 +05:30
Kyle Affolder 8c95c94472 update: combine to avoid multiple processes
Combined to reduce spawning multiple `rm` processes.
2025-02-17 13:29:34 -05:00
Kyle Affolder 7dafb2eddc add: GHA job to confirm if Dependabot PRs exist
Adds job to check if any Dependabot PRs are open and if so, fail the Docker build (since we need to ensure OS packages exist and are in their correct versions when using pinned versions for security purposes).
2025-02-17 13:24:22 -05:00
Kyle Affolder 7c9f3eb87f add: add back pinned tzdata version
Necessary for Dependabot to track
2025-02-17 13:22:15 -05:00
Kyle ab34dd81f8 Merge branch 'rajnandan1:main' into feature/dependabot-version-updates 2025-02-17 13:04:23 -05:00
Kyle Affolder 4e5fb26be1 add: CSS rule in EditorConfig 2025-02-17 12:53:29 -05:00
Kyle Affolder 6fc80bc0fc change: ensure full Lato font is kept from build
Ensures that the full variant of the Lato font-family is kept/removed from Docker image build. (Keeps the size small!)
2025-02-17 12:27:32 -05:00
Kyle Affolder 292667ac29 add: Lato webfont
Noticed multiple individuals commenting about insecure/privacy-unfriendly Lato webfont library being served via Google Fonts. I had formerly suggested replacing this with BunnyFonts and was happy to see that added as a placeholder, however, I also understand someone’s comment about this being loaded from an external resource.

This brings that webfont local. Size of webfont files should minimally grow Docker image sizes and I think we should prioritize UI and privacy by including it locally. The font’s licensing is OFL, so we are allowed to package it for distribution with this project.

I’m including both the full font family (for archival purposes) and Latin subset of this font. The Latin variant is used in the Docker image build (since this will apply to the majority of users and keep the Docker image smaller). If users need to extend this with their own subsets, they can always load those as a custom font. :)
2025-02-17 12:26:15 -05:00
Kyle Affolder 8c03058f8d update: combine to avoid multiple processes
Combined to reduce spawning multiple `rm` processes.
2025-02-17 12:21:57 -05:00
github-actions 25e73d097c Auto-generate README.md with release versions 2025-02-17 05:36:58 +00:00
Raj Nandan Sharma 1970cd8891 Merge pull request #291 from rajnandan1/fix/288-interpolation
Fix/288 interpolation
2025-02-17 10:51:34 +05:30
Raj Nandan Sharma 8ae9b2dec1 Bump version to 3.1.5 and update documentation layout 2025-02-17 10:48:22 +05:30
Raj Nandan Sharma 1662608984 Improves monitor status and data handling
Adds a NO_DATA status to handle cases where monitor data is unavailable.
Refactors data interpolation and aggregation logic for better accuracy and clarity.
Updates documentation links.

fixes #288
2025-02-17 10:47:47 +05:30
github-actions 42693983f1 Auto-generate README.md with release versions 2025-02-17 01:51:55 +00:00
Raj Nandan Sharma f43048d783 fix: update documentation version to 3.1.4 2025-02-17 07:06:35 +05:30
Raj Nandan Sharma b5ec332b4a Updates version to 3.1.3 and fixes group query
Updates the Kener version from 3.1.2 to 3.1.3.

Refactors the group query to use `havingRaw` for better compatibility across different database systems.

Adds database information to the bug report template.
2025-02-17 07:05:31 +05:30
github-actions dab1c44b18 Auto-generate README.md with release versions 2025-02-16 14:48:25 +00:00
Raj Nandan Sharma d9c0bff780 fix: update Dockerfile to remove specific tzdata version for compatibility 2025-02-16 19:56:15 +05:30
Raj Nandan Sharma beceace2c2 fix: update Dockerfile to correct documentation directory removal path 2025-02-16 19:23:45 +05:30
Raj Nandan Sharma 5373636704 Merge pull request #289 from rajnandan1/feature/category-status
Feature/category status
2025-02-16 19:09:22 +05:30
Raj Nandan Sharma e8d04eccf6 chore: bump version to 3.1.2 in package.json and update documentation layout 2025-02-16 19:08:01 +05:30
Raj Nandan Sharma d978c82263 Updates documentation and Dockerfile configuration
Updates documentation to reflect the new directory structure.
The documentation now correctly references images in the `/documentation` directory.
Removes the `src/static/documentation` directory in the Dockerfile.
2025-02-16 18:31:18 +05:30
Raj Nandan Sharma 883a458ed3 feat: add GroupCall service and integrate into monitoring logic #249 and #221 2025-02-16 12:23:58 +05:30
Raj Nandan Sharma 9009d9df99 refactor: streamline modal close logic and clear URL hash 2025-02-14 15:33:56 +05:30
Raj Nandan Sharma 81c0fa1243 fix: clear URL hash when modals are closed 2025-02-13 23:11:58 +05:30
github-actions 01221616ae Auto-generate README.md with release versions 2025-02-13 04:51:19 +00:00
Raj Nandan Sharma be75a0f5e9 Merge pull request #286 from rajnandan1/fix/seed-data-no-trackers
fix: update site metadata and image references for clarity
2025-02-13 10:05:46 +05:30
Raj Nandan Sharma 1b40839490 fix: update site metadata and image references for clarity 2025-02-13 10:02:18 +05:30
Raj Nandan Sharma 80f5780602 Merge pull request #284 from rajnandan1/feature/url-images
feat: Updates CSS and Svelte components
2025-02-12 22:23:16 +05:30
Raj Nandan Sharma dcf1817cd6 fix: add rel attribute to external link in monitor component 2025-02-12 22:21:41 +05:30
Raj Nandan Sharma ae251803ad refactor: remove unused sitemap generation code and update site metadata for clarity 2025-02-12 22:15:42 +05:30
Raj Nandan Sharma f6d9627ceb fix: update site title for clarity and conciseness 2025-02-12 22:07:58 +05:30
Raj Nandan Sharma 0cbc6b7b56 Update hero title for clearer branding
Updates hero title text to emphasize quick status page creation
and improves consistency in site configuration.
2025-02-12 22:05:51 +05:30
Raj Nandan Sharma f0af2d14e4 feat: Updates CSS and Svelte components
Enhances incident display with improved component structure.
2025-02-12 22:00:12 +05:30
github-actions b797039144 Auto-generate README.md with release versions 2025-02-12 02:49:23 +00:00
Kyle 86e29e5065 Merge pull request #283 from kaffolder7/fix/generate-readme-gha
fix(gha): generate-readme
2025-02-11 21:48:24 -05:00
Kyle Affolder fddef22e8e fix(gha): generate-readme
Was missing fallbacks in case repo variable was not set.
2025-02-11 21:47:52 -05:00
github-actions ad7551b7af Auto-generate README.md with release versions 2025-02-12 02:38:53 +00:00
Kyle Affolder a8be878a87 update: commented out node.js deps. monitoring
Not sure if we are wanting Dependabot to track Node.js packages, so for the time being, commenting this block out, but leaving for now w/ “TODO” to come back to at a later point.
2025-02-11 17:54:37 -05:00
Kyle Affolder 2b1849f1a3 update: grammar updates 2025-02-11 17:47:45 -05:00
Kyle Affolder 25a6590324 Merge branch 'feature/dependabot-version-updates' of https://github.com/kaffolder7/kener into feature/dependabot-version-updates 2025-02-11 17:31:48 -05:00
Kyle Affolder fa251dc07f update: README generation workflow 2025-02-11 17:31:33 -05:00
Kyle 4de93e7e95 Merge branch 'main' into feature/dependabot-version-updates 2025-02-11 17:25:28 -05:00
Kyle Affolder a5e5f33dc8 Merge branch 'main' into feature/dependabot-version-updates
# Conflicts:
#	.github/workflows/publish-images.yml
#	Dockerfile
2025-02-11 17:17:45 -05:00
Kyle f09eb18bcc Merge pull request #279 from kaffolder7/fix/readme-generation
fix: protect-readme workflow
2025-02-11 15:48:00 -05:00
Kyle 5c65e35f24 Merge branch 'rajnandan1:main' into fix/readme-generation 2025-02-11 15:46:45 -05:00
Kyle Affolder 8cbe859971 fix: protect-readme workflow 2025-02-11 15:46:05 -05:00
Kyle 67f38db847 Merge pull request #278 from kaffolder7/fix/readme-generation
fix: README generation
2025-02-11 15:41:12 -05:00
Kyle Affolder 28a72a3592 fix: README generation
Changed from trying to use artifacts and the GHA workflow failing to now using a simple `BUILD_VERSION` repository variable and automatically updating that when the `build-and-push-to-registries` workflow succeeds.

Other changes include:

* Added `workflow_run` trigger to `generate-readme.yml` so when that workflow recognizes the “Publish Docker Image to Registries” workflow runs and succeeds, it will automatically run the `generate-readme.yml` workflow (since a new Docker release will require Docker image variants table in README.md to have versioning updated)
* Generate major and major-minor versions from the `BUILD_VERSION` repository variable (more efficient than storing three separate variables from the `build-and-push-to-registries` workflow job)
2025-02-11 15:39:42 -05:00
Kyle dae334694d Merge pull request #274 from kaffolder7/feature/multi-arch-docker-build
manual readme update
2025-02-10 23:48:37 -05:00
Kyle e57fe3a046 Merge branch 'rajnandan1:main' into feature/multi-arch-docker-build 2025-02-10 23:48:04 -05:00
Kyle Affolder 54670c7845 fix: broken Docker latest ver. badge
Setting up to also pull latest ver. value from Mustache template
2025-02-10 23:47:28 -05:00
Kyle 3dbcef1500 Merge pull request #273 from kaffolder7/feature/multi-arch-docker-build
manual readme update
2025-02-10 23:36:18 -05:00
Kyle 24bbad05cb Merge branch 'rajnandan1:main' into feature/multi-arch-docker-build 2025-02-10 23:34:41 -05:00
Kyle Affolder cacbda3164 manual update
until I can get CI/CD pipeline debugged and fixed
2025-02-10 23:34:23 -05:00
Kyle da9b1a3a64 Merge pull request #272 from kaffolder7/feature/multi-arch-docker-build
fix: missing versions artifact
2025-02-10 23:25:38 -05:00
Kyle Affolder 394b911eca Merge branch 'feature/multi-arch-docker-build' of https://github.com/kaffolder7/kener into feature/multi-arch-docker-build 2025-02-10 23:25:03 -05:00
Kyle Affolder 220778e84f fix: missing versions artifact 2025-02-10 23:24:24 -05:00
Kyle c6e343da5d Merge pull request #271 from kaffolder7/feature/multi-arch-docker-build
fix: README build
2025-02-10 23:17:49 -05:00
Kyle 268e5b7478 Merge branch 'rajnandan1:main' into feature/multi-arch-docker-build 2025-02-10 23:17:13 -05:00
Kyle Affolder b9ccd16a02 fix: delete existing version artifact
* Delete existing version artifact if it exists
* Upload `versions.txt` only on first successful job
2025-02-10 23:16:20 -05:00
Kyle 302ad0e8a6 Merge pull request #270 from kaffolder7/feature/multi-arch-docker-build
fix: CI/CD README generation pipeline
2025-02-10 23:00:39 -05:00
Kyle Affolder e052f435db fix(README): docker versions table
Attempting to fix CI/CD pipeline so that README generation succeeds and versions are properly injected.
2025-02-10 22:58:55 -05:00
Kyle Affolder caa1330f39 remove: unnecessary attribute 2025-02-10 22:56:54 -05:00
Raj Nandan Sharma 870d19f566 docs: updated deployment docs 2025-02-11 08:39:09 +05:30
github-actions a28f787833 Auto-generate README.md with release versions 2025-02-11 01:55:21 +00:00
Raj Nandan Sharma 335a51b589 Merge pull request #265 from rajnandan1/release/3.0.13
feat: test monitors in manage monitor dashboard
2025-02-11 07:08:50 +05:30
Raj Nandan Sharma 72f9471486 chore: merged main with new release/3.1.0 2025-02-11 07:06:45 +05:30
Raj Nandan Sharma f0cd101af5 Merge branch 'main' into release/3.0.13 2025-02-11 07:04:12 +05:30
Raj Nandan Sharma 52876bd77e fix: fix #266 2025-02-11 07:01:17 +05:30
Raj Nandan Sharma aabe1926bd Merge pull request #268 from kaffolder7/feature/multi-arch-docker-build
fix: README generation
2025-02-11 06:55:28 +05:30
Kyle Affolder e785227064 Merge branch 'feature/multi-arch-docker-build' of https://github.com/kaffolder7/kener into feature/multi-arch-docker-build 2025-02-10 17:22:47 -05:00
Kyle Affolder 0cb88eec3b update: cleanup 2025-02-10 17:22:28 -05:00
Kyle 8cd2914f73 Merge branch 'rajnandan1:main' into feature/multi-arch-docker-build 2025-02-10 17:19:11 -05:00
Kyle Affolder f70e2ee8eb update: CI/CD pipeline
Changes include:

* Moving README generation to separate workflow (so that it can be trigger to run when any changes to `README.template.md` are pushed to `main` branch or a PR is opened with changes to template file
* GitHub Actions do not have privileges via `GITHUB_TOKEN` to commit to protected branches, thus, we need to take another approach and utilize a personal access token (which you’ll need to generate @rajnandan1) and add to the repository secrets (to avoid exposing that credential).
* Changes `publish-images` workflow to run now only when a new GitHub Release is created. (This will help prevent excessive workflow runs on merges into `main`)…in other words, @rajnandan1, you can merge freely into `main` now without excessive GitHub Actions usage.
2025-02-10 17:18:06 -05:00
Kyle c60a21ef32 Merge pull request #267 from kaffolder7/feature/multi-arch-docker-build
fix: multi-arch docker build
2025-02-10 16:19:01 -05:00
Kyle Affolder 77a57ee609 fix(docker): build issues
The following changes have been made:

* Ensured `package-lock.json` is up-to-date with latest dependencies from `package.json` - moved check to new workflow job and set as dependency for ‘build-and-push-to-registries’ job
* Updated branch-tagging for non-main branches (used when building Docker images)
* Restored pinned OS package versions in Dockerfile (for best-security)
* Restored “TODO” comments to Dockerfile (for tracking purposes and because I will revisit those items later this week)
* Added `—no-fund` tag to suppress npm package funding messages (helpful for CI/CD)
* Changed from `wget` to `curl` to resolve Debian package versioning issue between differing architectures (was one of the reasons causing the build to fail)
* As a part of the last comment, needed to then conditionalize container healthcheck logic
* Checked in newest `package-lock.json` file
* Fixed broken Docker badges in `README.template.md`
2025-02-10 16:15:08 -05:00
Kyle fc15f0e083 Merge branch 'rajnandan1:main' into feature/multi-arch-docker-build 2025-02-10 12:54:09 -05:00
Raj Nandan Sharma 6115beece3 fix: docker fix 2025-02-10 23:05:06 +05:30
Kyle Affolder 54434fbe78 fix: update package-lock.json
Hopefully will fix “npm error Missing: mustache@4.2.0 from lock file” error in broken GitHub Action docker build
2025-02-10 12:32:51 -05:00
Raj Nandan Sharma 83755bdd25 fix: docker fix 2025-02-10 22:59:07 +05:30
Raj Nandan Sharma 08f901c5f0 fix: docker fix 2025-02-10 22:53:30 +05:30
Raj Nandan Sharma 06910fbd4d fix: docker fix 2025-02-10 22:46:33 +05:30
Raj Nandan Sharma 154e7dd185 fix: docker fix 2025-02-10 22:41:18 +05:30
Raj Nandan Sharma 8bde3226bf Merge pull request #258 from kaffolder7/feature/multi-arch-docker-build
refactor(docker): multi-arch docker 🐳 build overhaul 🏗️
2025-02-10 22:04:28 +05:30
Kyle Affolder e5565145b5 fix(docker): action version number 2025-02-10 11:24:07 -05:00
Kyle Affolder 37a667daff fix(docker): dynamic README generation
🔄 Automate README Generation via Mustache Templating

- Use Mustache to dynamically generate `README.md` from `README.template.md`.
- Populate README with environment variables (e.g., `KENER_BUILD_FULL_VERSION`).
- Prevent direct edits to `README.md` by enforcing updates via the template.
- Enhance GitHub Actions workflow to auto-generate and commit the README.
- Add GitHub Action workflow (`protect-readme.yml`) to prevent others from direct updates to `README.md` via PR.
2025-02-10 11:21:22 -05:00
Kyle Affolder eda98bacfc update(docker): temporarily remove README updating
I caught an issue where the README will only auto-update listed Docker versions the first time. Commenting out for now (in case this PR gets merged before I have time to fix this). Will revisit this and fix this week.
2025-02-10 09:27:11 -05:00
Raj Nandan Sharma ee1ee52e13 feat: test monitors in manage monitor dashboard 2025-02-10 11:06:36 +05:30
Kyle Affolder 4060094404 add(dependabot): to automate dependency updates
Integrating Dependabot into the workflow ensures automatic dependency updates, improving security, reducing technical debt, and keeping packages up to date with minimal manual effort. This helps prevent vulnerabilities and maintain code stability over time.

Dependabot will automatically monitor the project’s dependencies and open pull requests (PRs) to update them when new versions are released. Here’s how it works:

1. Scans for Outdated Dependencies – It checks project dependency files (e.g., package.json, Dockerfile, .env.build, etc.) for outdated versions.
2. Fetches Latest Versions – When a newer version of a dependency is available, Dependabot retrieves it and updates the dependency files accordingly.
3. Opens a Pull Request – It then creates a PR with the updated dependency, detailing the changes and linking to release notes, changelogs, or security advisories.
4. Runs CI/CD Tests – If we end up setting up continuous integration (CI) tests, the PR will trigger the tests to check for breaking changes.
5. Security Updates – Dependabot also detects vulnerable dependencies and creates PRs to update them to a secure version.
6. Auto-Merging (Optional) – We might consider this at a later point, but if configured, it can automatically merge PRs when updates pass all tests and meet the requirements.
2025-02-09 22:08:06 -05:00
Kyle d03bf41ad1 Merge branch 'rajnandan1:main' into feature/multi-arch-docker-build 2025-02-09 21:56:54 -05:00
Raj Nandan Sharma ba2fe24629 Merge pull request #262 from rajnandan1/release/3.0.12
feat: adding hash params for modals in manage
2025-02-09 22:13:33 +05:30
Raj Nandan Sharma 547116090a feat: adding hash params for modals in manage 2025-02-09 22:11:07 +05:30
Raj Nandan Sharma 974826f42d Merge pull request #261 from rajnandan1/release/3.0.11
fix: handle ping migration
2025-02-09 19:49:46 +05:30
Raj Nandan Sharma 4914b029f9 fix: handle ping migration 2025-02-09 19:38:16 +05:30
Kyle Affolder ff864fbaab fix(README): restore accidentally removed tag
Noticed when doing some cleanup, that you had two awesome tags, but they both point to different URLs/repos. I added back in the one I had inadvertently removed.
2025-02-09 01:04:53 -05:00
Kyle Affolder f583ba4938 update(docker): README badge direct links
add direct links to filtered image(s) on Docker Hub, based on whether Debian or Alpine Linux variant badges are clicked
2025-02-09 00:44:28 -05:00
Kyle Affolder d552f541ac fix(docker): remove unnecessary files from build 2025-02-09 00:35:59 -05:00
Kyle d46d02e37a Merge branch 'main' into feature/multi-arch-docker-build 2025-02-09 00:26:21 -05:00
Raj Nandan Sharma 4e0c6e85da style: added more readme badges 2025-02-09 09:31:01 +05:30
Raj Nandan Sharma 1147808366 Merge pull request #256 from kaffolder7/feature/small-improvements
feat: small improvements
2025-02-09 09:04:13 +05:30
Kyle a7c27a60e0 Merge branch 'rajnandan1:main' into feature/multi-arch-docker-build 2025-02-08 15:32:48 -05:00
Kyle Affolder 974976bd90 update(docker): expanded on existing examples 2025-02-08 15:31:32 -05:00
Kyle Affolder 9bbe665984 update(docker): add TODO comments for future work 2025-02-08 14:22:16 -05:00
Kyle Affolder d0ea8551b6 update(docker): add TODO comments for future work 2025-02-08 13:56:11 -05:00
Kyle Affolder 089ee9bc07 update(formatting): made consistent
Aligned `.prettierrc` and `.editorconfig` files to same, best-practice & most-widely-adopted standards.
2025-02-08 13:39:41 -05:00
Raj Nandan Sharma 786c4f8207 fix: fixed cookie getting set in different paths from client side when language was changed^C 2025-02-08 21:37:18 +05:30
Raj Nandan Sharma 13879aefdd Merge pull request #253 from VincentDatrier/main
Add Norwegian (Bokmål) as an available locale
2025-02-08 17:01:07 +05:30
Kyle Affolder d03a8fd7a4 update(README): fix typo 2025-02-08 04:52:17 -05:00
Kyle Affolder c00aae5566 update(README): adjusted emoji spacing 2025-02-08 04:50:06 -05:00
Kyle Affolder e923f4d650 update(README): proofread and polished ☺️ 2025-02-08 04:48:30 -05:00
Kyle Affolder 654c07a364 fix(README): table subheading alignment 2025-02-08 04:19:34 -05:00
Kyle Affolder 820eeb0aac fix(README): table subheading alignment 2025-02-08 04:18:27 -05:00
Kyle Affolder 2a338baa26 fix(README): remove broken icons that were added 2025-02-08 04:17:16 -05:00
Kyle Affolder ec7351272f add(docker): missing entrypoint.sh file 2025-02-08 04:13:47 -05:00
Kyle Affolder 13dec43ef3 update(docker): expanded docker readme section
* Expanded upon existing Docker README section.
* Created table which will contains version placeholder variables that will be replaced by new GitHub workflow job: “update_readme”. Job automatically runs after new images have been built & pushed to container registries.
2025-02-08 04:12:50 -05:00
Kyle Affolder 43673e3349 add(workflow): allow admin to manually build 2025-02-08 04:08:45 -05:00
Kyle Affolder 1e77253a63 update(docker): healthcheck port & path + cleanup 2025-02-08 02:12:31 -05:00
Kyle Affolder 798af326a2 fix: update docs link to online documentation
Default documentation link in main nav won’t work because /docs are not included in built Docker images (to keep image smaller). Instead, changing seed data to point to the docs homepage. :)
2025-02-08 00:13:49 -05:00
Kyle Affolder d349a7591e update(docker): ver. pinning, healthcheck, etc.
* add: version pinning (better stability)
* remove: unnecessary KENER_BASE_PATH env. variable
* update: reduce permissions of /uploads and /database directories
* add: `entrypoint.sh` file
* add: properly map container timezone and localtime
* add: container healthcheck
* change: restrict to non-root “node” user
2025-02-08 00:04:19 -05:00
Kyle Affolder 8370145f6c update: change fonts API
Switching from Google Fonts to Bunny Fonts CDN. Bunny Fonts is an open-source, privacy-first web font platform. It is fully GDPR compliant (Google is not) and can act as a drop-in replacement for Google Fonts.
2025-02-07 20:14:45 -05:00
Kyle 304945acea Merge branch 'rajnandan1:main' into feature/small-improvements 2025-02-07 19:51:50 -05:00
Kyle Affolder 1dd05fee50 update: tweaked ordering of env variables 2025-02-07 14:58:33 -05:00
Kyle 374eda4103 Merge branch 'rajnandan1:main' into feature/multi-arch-docker-build 2025-02-07 14:55:05 -05:00
Kyle Affolder 52a8cca3e8 fix(docker): broken build & run as non-root user 2025-02-07 13:06:17 -05:00
Vincent Datrier 22ef4d75b6 Trigger GitHub Actions 2025-02-07 14:11:39 +01:00
Vincent Datrier 534e6a0ad3 Updated nb-NO locale with new strings 2025-02-07 13:46:32 +01:00
Vincent Datrier 80ed4fc5fa Merge branch 'main' of github.com:VincentDatrier/kener 2025-02-07 13:42:06 +01:00
Raj Nandan Sharma 445eb02386 chore: optimized seo 2025-02-07 10:24:06 +05:30
Raj Nandan Sharma 53ae0b89b0 chore: optimized seo 2025-02-07 10:16:49 +05:30
Raj Nandan Sharma c7376b4d8f Merge pull request #250 from rajnandan1/release/3.0.10
Release/3.0.10
2025-02-07 09:13:20 +05:30
Raj Nandan Sharma e3e59b7e24 feat: adding tcp monitor, reinstating ping as reported in #243 2025-02-07 09:12:15 +05:30
Raj Nandan Sharma 07f59ac581 feat: adding tcp monitor, reinstating ping as reported in #243 2025-02-07 09:02:28 +05:30
Kyle Affolder 7176c3d4f4 refactor(gha): update gh publish image workflow
Streamlined the GitHub `publishImage.yml` workflow with the following functionality:

* Handle both Alpine and Debian variants through matrix strategy
* Push to both Docker Hub and GitHub Container Registry
* Add comprehensive tagging strategy, handling both branches (aka release version, e.g. 1.0.0), semantic versions (major.minor and major), and latest versions (`latest` and `alpine`)
* Add security aspects (cosign signing, proper permissions)
* Add better caching and multi-platform build settings

With this revised workflow, the following Docker image variants will be built for every successful release. As an example, if the release version is “3.0.9”, then the following Docker image variants will be built:

Debian variants (default):
- `kener:3.0.9` (Semver of current release)
- `kener:latest` (Latest Debian release, ’latest’ label points to 3.0.9)
- `kener:3.0` (major.minor version, major.minor ‘3.0’ label points to 3.0.9)
- `kener:3` (major version, major ‘3’ label points to 3.0.9)

Alpine variants (smallest filesize):
- `kener:3.0.9-alpine` (Semver of current release)
- `kener:alpine` (Latest Alpine release, ‘alpine’ label points to 3.0.9)
- `kener:3.0-alpine` (major.minor version, major.minor ‘3.0-alpine’ label points to 3.0.9)
- `kener:3-alpine` (major version, major ‘3-alpine’ label points to 3.0.9)
2025-02-05 22:24:09 -05:00
Kyle Affolder 13366284c6 add(docker): docker-specific README badges 2025-02-05 21:03:18 -05:00
Kyle Affolder 0f0b447137 remove: forks badge from README 2025-02-05 20:47:51 -05:00
Kyle Affolder f7cc28c896 add: badges to README 2025-02-05 20:45:10 -05:00
Kyle Affolder fd790003d1 add: editorconfig to enforce consistent styles 2025-02-05 18:40:10 -05:00
Kyle Affolder fdad329148 update(docker): simplified variable name 2025-02-05 18:37:10 -05:00
Kyle Affolder 73bf5f3fbe refactor(docker): improve build w/ multistage
* Switch to multi-stage build pattern for smaller image size
* Add support for both Alpine and Debian variants via build args
* Change default image base to `node:23-slim` instead of using `node:23` (no need for full Debian base present in `node:23` since now prioritization is given to production-ready builds)
* Improve caching with --mount for npm dependencies
* Separate build and runtime dependencies
* Remove unnecessary Node.js packages in final stage
* Fix permissions on uploads/database directories
* Add proper scoping for build arguments
* Set NODE_ENV=production for better performance

This change reduces the final image size and improves build caching while adding flexibility to choose between Alpine and Debian base images.

Original: ~1.2GB
New Alpine: ~350MB
New Debian: ~450MB
2025-02-05 14:03:46 -05:00
Raj Nandan Sharma 103d64a659 fix: fix bug where incident status is not getting updated when adding comment #245 2025-02-05 22:43:33 +05:30
Raj Nandan Sharma 92c4d35992 fix: fix bug where incident status is not getting updated #246 2025-02-05 22:42:29 +05:30
Raj Nandan Sharma 3b1d95b71b Merge pull request #241 from kaffolder7/feature/suppress-warnings-production-build
Update: Suppress warnings in production build
2025-02-05 09:46:20 +05:30
Raj Nandan Sharma 4fd9bf2bb6 Merge pull request #234 from rajnandan1/release/3.0.9
feat: support SMTP for email trigger
2025-02-05 09:17:39 +05:30
Raj Nandan Sharma ce96b6f55d docs: update i18n 2025-02-05 09:17:11 +05:30
Raj Nandan Sharma 54bbc1dd00 chore: update i18n 2025-02-05 08:59:07 +05:30
Raj Nandan Sharma ffa31bcacc docs: updated roadmap doc 2025-02-05 08:09:49 +05:30
Kyle Affolder 559f5bd257 Update: Suppress warnings in production build
When building for production, various warnings are output which slows down production build.

The following changes were made:
- Suppress unused export properties (unused-export-let).
- Suppress conflicting Svelte resolve warnings (conflicting-svelte-resolve).
- Suppress empty chunk warnings (empty-chunk).
- Suppress unused module imports (module-unused-import).
- Keep other important warnings visible, so we’re still aware of potential issues.

Now, production build should be cleaner and faster! 🚀
2025-02-04 16:03:10 -05:00
Raj Nandan Sharma 977e49e1ce feat: seo fixes 2025-02-04 23:18:04 +05:30
Raj Nandan Sharma 30cb707436 feat: added category filter for view monitor #239 2025-02-04 21:37:00 +05:30
Raj Nandan Sharma ae439633b9 feat: added category filter for view monitor #239 2025-02-04 21:34:47 +05:30
Vincent Datrier 7f33f6ddfd Add Norwegian (Bokmål) as an available locale 2025-02-04 14:48:25 +01:00
Raj Nandan Sharma 8404415a93 feat: eval in ping #236 and port number in ping #211 2025-02-04 10:34:00 +05:30
Raj Nandan Sharma 5ddddf8b5d fix: bugs and documentation update as mentioned in #237 2025-02-03 21:33:07 +05:30
Raj Nandan Sharma 927db19cc6 feat: smtp autofill and docs update 2025-02-03 11:46:25 +05:30
Raj Nandan Sharma eccff16c5f feat: introducing event type maintenance as asked in #224 2025-02-02 23:03:58 +05:30
Raj Nandan Sharma 7be9c62c7c fix: allow longer TLD as reported in #235 2025-02-01 20:48:42 +05:30
Raj Nandan Sharma 76ce14e8b1 feat: support SMTP for email trigger 2025-02-01 16:11:27 +05:30
Raj Nandan Sharma fd0074c0b0 Merge pull request #233 from rajnandan1/release/3.0.8
fix: discord trigger fix #232
2025-01-31 20:22:43 +05:30
Raj Nandan Sharma ad01cdb5ac fix: discord trigger fix #232 2025-01-31 20:19:27 +05:30
Raj Nandan Sharma 1b4ca67cf0 Merge pull request #231 from rajnandan1/release/3.0.7
feat(triggers): support custom webhook body as requested in #230
2025-01-31 09:10:18 +05:30
Raj Nandan Sharma 53936e19d4 feat(triggers): support custom webhook body as requested in #230 2025-01-31 09:08:26 +05:30
Raj Nandan Sharma b58d00c5a6 feat(triggers): support custom webhook body as requested in #230 2025-01-31 09:03:57 +05:30
Raj Nandan Sharma 41db0c45cd Merge pull request #229 from YunusEmreAlps/feature/locales-tr
feat: add Turkish locale support and update locale files
2025-01-30 19:43:55 +05:30
Yunus Emre Alpu 5361b551eb feat: add Turkish locale support and update locale files 2025-01-30 13:05:08 +03:00
Raj Nandan Sharma eb87647466 Merge pull request #228 from rajnandan1/release/3.0.6
feat: support of promises in eval
2025-01-29 10:09:35 +05:30
Raj Nandan Sharma d9b900f28d feat: support of promises in eval 2025-01-28 23:37:26 +05:30
Raj Nandan Sharma 198bd6c723 Merge pull request #227 from matribeiro15/main
Update pt-BR.json
2025-01-28 22:44:26 +05:30
Mateus Ribeiro 1831e6309f Update pt-BR.json 2025-01-28 12:32:22 -03:00
Raj Nandan Sharma 007f5149d2 Update README.md 2025-01-27 23:07:06 +05:30
Raj Nandan Sharma 1a094e3511 Merge pull request #226 from rajnandan1/release/3.0.5
fix: locale for no incidents as reported in #225
2025-01-27 22:39:19 +05:30
Raj Nandan Sharma db1ddc1292 fix: locale for no incidents as reported in #225 2025-01-27 22:37:54 +05:30
Raj Nandan Sharma 535e3bb36a fix: hi.json fixed 2025-01-26 16:12:08 +05:30
Raj Nandan Sharma 785237e55e Merge pull request #223 from cosmic-jellyfish/feature/Further-Locale-Fixes
fix: localisation strings for further clarity
2025-01-26 16:08:04 +05:30
Raj Nandan Sharma e6bf47a859 docs: deployment docs 2025-01-26 15:13:46 +05:30
Raj Nandan Sharma 93ae1711f1 docs: deployment docs 2025-01-26 15:04:57 +05:30
Raj Nandan Sharma af97812beb docs: railway deploy button added 2025-01-26 13:12:38 +05:30
Cake 78cf22ade9 fix: correct spacing 2025-01-26 08:09:48 +11:00
Cake b4a461d93a fix: localisation strings for further clarity 2025-01-26 08:04:37 +11:00
Raj Nandan Sharma 5f33c02064 Merge pull request #222 from rajnandan1/release/3.0.4
Release/3.0.4
2025-01-25 21:43:05 +05:30
Raj Nandan Sharma 46c1a392b8 refactor: remove docs from docker build 2025-01-25 21:41:11 +05:30
Raj Nandan Sharma c6880c5df6 feat: sitemap and bug fixes 2025-01-25 21:18:42 +05:30
Raj Nandan Sharma 646da94ef9 feat: sitemap and bug fixes 2025-01-25 21:16:17 +05:30
Raj Nandan Sharma 36ede93dce feat: sitemap and bug fixes 2025-01-25 21:12:41 +05:30
Raj Nandan Sharma 9ed35589f7 Merge pull request #220 from rajnandan1/release/3.0.3
fix: load time #219
2025-01-24 21:45:33 +05:30
Raj Nandan Sharma e946dd18e0 fix: load time #219 2025-01-24 21:44:36 +05:30
Raj Nandan Sharma 51cad598c1 fix: load time #219 2025-01-24 21:42:57 +05:30
Raj Nandan Sharma 0595f23c18 Merge pull request #218 from cosmic-jellyfish/feature/ENG-Locale-Fix
English Locale tweaks
2025-01-24 13:04:58 +05:30
Raj Nandan Sharma b88816706b fix: retry await decrease from 4000 to 500ms 2025-01-24 11:42:06 +05:30
Cake 80edaae023 fix: update localisation strings across the board 2025-01-24 17:08:09 +11:00
Raj Nandan Sharma 883c46b064 Merge pull request #214 from rajnandan1/release/3.0.2
release of 3.0.2
2025-01-24 10:48:15 +05:30
Raj Nandan Sharma 4248cc31f2 chore: updated package.json version 2025-01-24 10:47:56 +05:30
Raj Nandan Sharma 5f81c6d61a fix: daily data local date format 2025-01-24 10:45:50 +05:30
Raj Nandan Sharma 00d82cc817 fix: daily data local date format 2025-01-24 10:44:48 +05:30
Raj Nandan Sharma a9edfde162 fix: bug fix for no data 2025-01-24 10:35:22 +05:30
Cake 468d7f445d fix: update localisation strings further 2025-01-24 15:15:13 +11:00
Cake 4cc74deece fix: update localisation strings 2025-01-24 15:11:50 +11:00
Raj Nandan Sharma f9831490af fix: base path for docker build 2025-01-24 09:08:13 +05:30
Raj Nandan Sharma a055a616eb fix: fix logo url as reported in #213 2025-01-23 23:37:39 +05:30
Raj Nandan Sharma 8009a2cbc4 feat: monitor re-arrange as requested in #215 2025-01-23 22:39:13 +05:30
Raj Nandan Sharma 693cc149cf feat: added retry for api timeouts as requested in #208 2025-01-23 10:33:15 +05:30
Raj Nandan Sharma d126fe0bce fix: fix for #212, data interpolation introduced 2025-01-23 09:36:37 +05:30
Raj Nandan Sharma 4cc49cce9e fix: default locale fix as reported in #209 2025-01-22 09:25:56 +05:30
Raj Nandan Sharma f7cb4cd805 fix: cron validation updated as reported in #206 2025-01-21 22:02:39 +05:30
Raj Nandan Sharma a2bd9883d1 chore: remove old docs.md 2025-01-21 09:18:21 +05:30
Raj Nandan Sharma 10a079c304 fix: fixing #193 base path login was having double slash in cookie 2025-01-21 09:12:10 +05:30
Raj Nandan Sharma 65f12f1082 docs: added contributing file 2025-01-21 08:44:58 +05:30
Raj Nandan Sharma c15f855c72 docs: added contributing file 2025-01-21 08:42:41 +05:30
Raj Nandan Sharma 3d38483022 Merge pull request #205 from matribeiro15/main
Add Brazilian Portuguese
2025-01-21 08:31:33 +05:30
Mateus Ribeiro 57dc1e7175 modified: src/lib/i18n/client.js
modified:   src/lib/locales/locales.json
	new file:   src/lib/locales/pt-BR.json
2025-01-21 01:22:01 +00:00
Raj Nandan Sharma ead68c929f docs: embed docs 2025-01-18 23:19:38 +05:30
Raj Nandan Sharma d54b346259 Merge pull request #204 from rajnandan1/date-fns
feat: i18n for dates
2025-01-18 23:14:13 +05:30
Raj Nandan Sharma a05de8269c docs: updated feature list 2025-01-18 23:13:13 +05:30
Raj Nandan Sharma 130b70c6af feat: i18n for dates 2025-01-18 23:10:07 +05:30
Raj Nandan Sharma 7247a54c08 feat: i18n for dates 2025-01-18 22:46:15 +05:30
Raj Nandan Sharma 28de7ebff8 feat: i18n for dates 2025-01-18 22:07:54 +05:30
Raj Nandan Sharma e2c9d4dc2d feat: i18n for dates 2025-01-18 21:44:46 +05:30
Raj Nandan Sharma 4866382180 feat: i18n for dates 2025-01-18 21:36:51 +05:30
Raj Nandan Sharma 1b1507db1a Merge pull request #202 from rajnandan1/heroku-dep
fix: fixes #198
2025-01-18 12:40:12 +05:30
Raj Nandan Sharma 0ea7d687a7 fix: fixes #198 2025-01-18 12:39:05 +05:30
Raj Nandan Sharma 502e1539ca Merge pull request #201 from sandyi5/main
korean translation update for v 3.0
2025-01-18 10:31:24 +05:30
김세영 f3910fea1b Update ko.json 2025-01-18 12:44:33 +09:00
김세영 ee9b48fb79 Create ko.json 2025-01-18 12:43:39 +09:00
김세영 142686ef92 Update locales.json 2025-01-18 12:24:57 +09:00
Raj Nandan Sharma 2aa187984e fix: cookie secure fix 2025-01-17 21:26:55 +05:30
Raj Nandan Sharma c20d4d71c3 fix: cookie secure fix 2025-01-17 20:37:02 +05:30
Raj Nandan Sharma 4f4d1e1b39 fix: login redirection to setup if not done 2025-01-17 09:51:07 +05:30
Raj Nandan Sharma f38b590f70 fix: handle file uplaod for docker container 2025-01-17 08:48:21 +05:30
Raj Nandan Sharma c2caf42fd3 Merge pull request #195 from rajnandan1/fix/day0
Fix/day0
2025-01-17 08:44:52 +05:30
Raj Nandan Sharma 8c279c8c10 fix: fixed bugs reported in reddit around analytics and css 2025-01-17 08:43:35 +05:30
Raj Nandan Sharma 3480c78360 fix: fixed bugs reported in reddit around analytics and css 2025-01-17 08:39:29 +05:30
Raj Nandan Sharma 79ce708f60 fixes reported on reddit 2025-01-16 23:13:47 +05:30
Raj Nandan Sharma a4526eb4b5 fix: nav list in home manage 2025-01-15 22:25:23 +05:30
Raj Nandan Sharma 866a133917 Merge pull request #194 from jghaanstra/main 2025-01-15 22:16:30 +05:30
Jelger Haanstra adce978244 Add Dutch Translation 2025-01-15 17:42:37 +01:00
Raj Nandan Sharma dd3c26b29a fix: fixing #193 and #191 2025-01-15 22:02:15 +05:30
Raj Nandan Sharma 25ad42ba9b Merge pull request #190 from otherwiseGG/danish-customer-language
Danish Translation
2025-01-15 21:29:56 +05:30
Benjamin Thiele 3285b7e472 Merge branch 'main' into danish-customer-language 2025-01-15 16:43:28 +01:00
Raj Nandan Sharma c7da55480a docs(docker): updated roadmap 2025-01-15 09:35:14 +05:30
Raj Nandan Sharma 35e81b5782 fix(docker): fixing #188 and #189
CHANGE: startup script started running before migrations of database, moved it to after migration
2025-01-15 09:24:05 +05:30
Raj Nandan Sharma 769a9526e7 Merge pull request #187 from kutovoys/main
Add Russian translations and update locales.json
2025-01-15 08:59:43 +05:30
Benjamin Thiele 5470153f4f Merge branch 'main' into danish-customer-language 2025-01-15 00:15:52 +01:00
Benjamin Thiele 9d6ad87275 Create dk.json 2025-01-15 00:13:55 +01:00
Benjamin Thiele ac6261d9ac Update locales.json 2025-01-15 00:12:18 +01:00
Sergey Kutovoy 48effe077e Add Russian translations and update locales.json
- Added Russian language support by creating ru.json with translations for various terms and phrases.
- Updated locales.json to include Russian in the list of available languages.
2025-01-15 00:48:57 +05:00
Raj Nandan Sharma c656bf8612 Merge pull request #186 from otherwiseGG/patch-1 2025-01-14 23:43:42 +05:30
Benjamin Thiele 9daf7c7156 Update locales.json 2025-01-14 18:53:27 +01:00
Benjamin Thiele 4bd3bfae9d Create de.json
German Translation File
2025-01-14 18:51:38 +01:00
Raj Nandan Sharma 727964949c docs: updated i18n docs 2025-01-14 23:10:24 +05:30
Raj Nandan Sharma 21e1fc1aca fix: fix seo meta tags 2025-01-14 22:49:00 +05:30
Raj Nandan Sharma eeba2ef2d4 docs: updated readme file 2025-01-14 22:37:06 +05:30
Raj Nandan Sharma b3e738ce03 fix: upload for base path aslo fixed 2025-01-14 22:01:30 +05:30
Raj Nandan Sharma 46848c7b19 fix: file upload issue 404 2025-01-14 21:49:56 +05:30
Raj Nandan Sharma 5f3d48597f fix: sitemap fixed.removed for now 2025-01-14 21:25:33 +05:30
Raj Nandan Sharma 0efc3e5999 Merge pull request #178 from rajnandan1/release/2.0.1
Release/3.0.0
2025-01-14 21:12:51 +05:30
Raj Nandan Sharma 0c0ce89317 docs: changelogs 2025-01-14 21:11:58 +05:30
Raj Nandan Sharma 4a47061f76 fix: deployment 2025-01-14 21:09:50 +05:30
Raj Nandan Sharma 9b3ff9b362 fix: deployment 2025-01-14 21:08:29 +05:30
Raj Nandan Sharma 8315fede7d docs: updated docs for release 2025-01-14 20:22:14 +05:30
Raj Nandan Sharma d6d0568f67 docs: updated docs for release 2025-01-14 17:57:42 +05:30
Raj Nandan Sharma ff0119db9b fix: fix embed and sub path 2025-01-14 14:07:53 +05:30
Raj Nandan Sharma 37d776c315 refactor: move accounts pages under manage and forgot password 2025-01-14 07:46:13 +05:30
Raj Nandan Sharma f53d4abd8b fix: responsiveness 2025-01-13 11:31:10 +05:30
Raj Nandan Sharma f827bd4aeb feat: i18n added french #179, removed github dependency, clean up old code, simpler i18n file 2025-01-13 10:57:12 +05:30
Raj Nandan Sharma f28c4e96c5 feat: db clean up 2025-01-12 16:22:39 +05:30
Raj Nandan Sharma cf81b11c0b feat: support for postgres using knex 2025-01-12 14:07:37 +05:30
Raj Nandan Sharma 17dc752902 feat: support for postgres using knex 2025-01-12 13:41:17 +05:30
Raj Nandan Sharma bc9faf9456 feat: alerting with new incidents 2025-01-10 08:59:11 +05:30
Raj Nandan Sharma e5b615267c feat: incidents api 2025-01-09 12:03:54 +05:30
Raj Nandan Sharma 70e9086646 feat: incidents from github to sqlite 2025-01-08 23:14:59 +05:30
Raj Nandan Sharma 0cc30bc67e feat: incidents from github to sqlite 2025-01-07 11:57:40 +05:30
Raj Nandan Sharma 73792e7ce6 feat: incidents from github to sqlite 2025-01-07 11:39:38 +05:30
Raj Nandan Sharma 32f873d9c2 feat: incidents from github to sqlite 2025-01-06 09:56:34 +05:30
Raj Nandan Sharma 6566bc5f8f build: docker file update 2025-01-03 11:48:18 +05:30
Raj Nandan Sharma cc93114eab docs: adding docs for 3.0.0 2025-01-02 11:20:28 +05:30
Raj Nandan Sharma 6d5d949f5c docs: adding docs for 3.0.0 2024-12-29 23:16:11 +05:30
Raj Nandan Sharma f1be4a4db0 feat: pre release 3.0.0 2024-12-28 19:21:23 +05:30
Raj Nandan Sharma 8b9f576b30 feat: pre release 3.0.0 2024-12-27 16:58:51 +05:30
Raj Nandan Sharma 0735f959ef feat: pre release 3.0.0 2024-12-27 09:16:30 +05:30
423 changed files with 43570 additions and 16750 deletions
+9
View File
@@ -0,0 +1,9 @@
node_modules
.git
.github
.vscode
dist
build
.env
.DS_Store
*.log
+58
View File
@@ -0,0 +1,58 @@
# EditorConfig helps maintain consistent coding styles between editors
root = true
# Default settings for all files (e.g. most common, best-practice standard across all filetypes)
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = space
indent_size = 2
# Svelte files
[*.svelte]
indent_style = space
indent_size = 2
trim_trailing_whitespace = false
# JavaScript and TypeScript
[*.{js,ts,tsx,cjs,mjs}]
indent_style = space
indent_size = 2
# JSON files (package.json, config files, etc.) - per JSON (RFC 8259) specification
[*.json,.prettierrc]
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = false
# YAML files (e.g., GitHub Actions, Lint configs) - per YAML 1.2 (2009) specification
[*.{yaml,yml}]
indent_style = space
indent_size = 2
# CSS & PostCSS files
[*.{css,postcss}]
indent_style = space
indent_size = 2
# Markdown files
[*.md]
indent_style = space
indent_size = 4
trim_trailing_whitespace = false
print_width = 180
# Dockerfile
[Dockerfile*]
indent_style = tab
indent_size = 4
insert_final_newline = false
# Ignore binary files
[*.{png,jpg,jpeg,gif,ico,svg,woff,woff2,eot,ttf,otf}]
charset = unset
trim_trailing_whitespace = false
insert_final_newline = false
+26 -7
View File
@@ -1,7 +1,26 @@
NODE_ENV=production
PORT=3000
GH_TOKEN=your_github_token
API_TOKEN=your_api_token
API_IP=""
API_IP_REGEX=""
KENER_BASE_PATH=""
TZ=Etc/UTC
KENER_SECRET_KEY=please_change_me_to_something_secure
# For SQLite database...
DATABASE_URL=sqlite://./database/kener.sqlite.db
# For PostgreSQL database...
# DATABASE_URL=postgresql://db_user:db_password@localhost:5432/kener_db
# POSTGRES_PASSWORD=some_super_random_secure_password
# For MySQL database...
# DATABASE_URL=mysql://db_user:db_password@127.0.0.1:3306/kener_db
# MYSQL_PASSWORD=some_super_random_secure_password
KENER_BASE_PATH=""
ORIGIN=http://localhost:3000
RESEND_API_KEY=""
RESEND_SENDER_EMAIL=Accounts <accounts@resend.dev>
# Likely no need to change...
# NODE_ENV=production # already defined in container
# PORT=3000 # default port Kener service is exposed upon
# Add the below variable if you would like to white-label the product (aka. remove some of the attributions scattered throughout the app)
# WHITE_LABEL=true
+48
View File
@@ -0,0 +1,48 @@
# Contributing to Kener
Thank you for considering contributing to our project! Here are some guidelines to help you get started.
---
## How to Contribute
1. Fork the repository and clone it locally.
2. Create a new branch for your feature or bug fix:
```bash
git checkout -b feature/your-feature-name
```
3. Make your changes and commit them:
```bash
git commit -m 'Describe your changes'
```
4. Push your changes to your fork:
```bash
git push origin feature/your-feature-name
```
5. Create a pull request to the `main` branch.
## Development
1. Install dependencies:
```bash
npm install
```
2. Create a `.env` file in the root of the project and add the following:
```bash
cp .env.example .env
```
2. Start the development server:
```bash
npm run dev
```
3. Open [http://localhost:3000](http://localhost:3000) in your browser.
## Documentation
The documentation is available in the `docs` folder. You can view it by going to [http://localhost:3000/docs/home](http://localhost:3000/docs/home) in your browser.
## Where to Start
1. Check out the [roadmap items](https://kener.ing/docs/roadmap/)
2. Add language support by following the [i18n guide](https://kener.ing/docs/i18n/)
+9 -11
View File
@@ -17,22 +17,20 @@ Steps to reproduce the behavior:
3. Scroll down to '....'
4. See error
**Version**
Which version of kener you are using.
**Environment**
Which environment you are using or where is it deployed. `docker`, `kubernetes`, `bare-metal`, `development`, `pm2` etc
**Database**
Which database you are using. `sqlite`, `mysql`, `postgres`
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.
@@ -1,13 +0,0 @@
---
name: Create Incident Template
about: Create Incident Template
title: Title of Incident
labels: incident
assignees: ''
---
Your Incident Description goes here. Markdown Supported
[start_datetime:utcSeconds]
[end_datetime:utcSeconds]
+47
View File
@@ -0,0 +1,47 @@
version: 2
updates:
# Track base image versions via .env.build
- package-ecosystem: "docker"
directory: "/"
schedule:
interval: "weekly"
file-patterns:
- ".env.build"
- "node:*" # Ensures Node.js images are correctly detected
# Monitor OS package versions in Dockerfile (Debian/Alpine)
- package-ecosystem: "gitsubmodule" # Alternative method to track OS packages in Dockerfile
directory: "/"
schedule:
interval: "weekly"
labels:
- "dependencies"
- "os-packages"
commit-message:
prefix: "os"
include: "scope"
# Monitor Node.js dependencies from package.json
# TODO: Uncomment below if we want to begin letting Dependabot monitor & open PRs for Node.js project dependencies
# - package-ecosystem: "npm"
# directory: "/"
# schedule:
# interval: "weekly"
# labels:
# - "dependencies"
# - "npm"
# commit-message:
# prefix: "npm"
# include: "scope"
# Monitor GitHub Actions dependencies
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
labels:
- "dependencies"
- "github-actions"
commit-message:
prefix: "actions"
include: "scope"
+83
View File
@@ -0,0 +1,83 @@
name: Generate README
on:
push:
branches:
- main
paths:
- 'README.template.md'
pull_request:
branches:
- main
paths:
- 'README.template.md'
workflow_dispatch: # Allows for manual execution
workflow_run: # Triggers this workflow to run when it recognizes 'publish-images' workflow has ran and successfully completed
workflows: ["Publish Docker Image to Registries"]
types:
- completed
permissions:
contents: write # Explicitly allow pushing changes
jobs:
generate-readme:
name: Generate README from template
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4.2.2
with:
fetch-depth: 0 # Fetch full history, including tags
persist-credentials: false # We'll manually authenticate
- name: Configure Git
run: |
git config --global user.name "github-actions"
git config --global user.email "github-actions@github.com"
- name: Setup Node.js
uses: actions/setup-node@v4.2.0
with:
node-version: "20"
- name: Install Dependencies
run: npm install mustache dotenv
- name: Extract Major and Major-Minor Versions
run: |
VERSION="${{ vars.BUILD_VERSION }}"
# Check if VERSION is empty and set a fallback value
if [ -z "$VERSION" ]; then
# Fetch the latest release using Git
VERSION=$(git tag -l --sort=-v:refname | grep -E '^v?[0-9]+\.[0-9]+\.[0-9]+$' | head -n 1 || echo "3.1.0")
fi
MAJOR=$(echo "$VERSION" | cut -d. -f1)
MAJOR_MINOR=$(echo "$VERSION" | cut -d. -f1,2)
echo "Full version: $VERSION"
echo "Major version: $MAJOR"
echo "Major-Minor version: $MAJOR_MINOR"
# Export all as environment variables
echo "LATEST_VERSION=$VERSION" >> $GITHUB_ENV
echo "LATEST_MAJOR_VERSION=$MAJOR" >> $GITHUB_ENV
echo "LATEST_MAJOR_MINOR_VERSION=$MAJOR_MINOR" >> $GITHUB_ENV
- name: Generate README.md
env:
BUILD_FULL_VERSION: ${{ env.LATEST_VERSION }} # e.g., 1.2.3
BUILD_MAJOR_VERSION: ${{ env.LATEST_MAJOR_VERSION}} # e.g., 1
BUILD_MAJOR_MINOR_VERSION: ${{ env.LATEST_MAJOR_MINOR_VERSION }} # e.g., 1.2
run: node scripts/generate-readme.js
- name: Commit and Push Changes
env:
GH_PAT: ${{ secrets.GH_PAT }}
run: |
git add README.md
git commit -m "Auto-generate README.md with release versions" || echo "No changes to commit"
git push https://x-access-token:${{ secrets.GH_PAT }}@github.com/${{ github.repository }}.git HEAD:main
+21
View File
@@ -0,0 +1,21 @@
name: Prevent Direct README Changes
on:
pull_request:
paths:
- "README.md"
jobs:
check-readme:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4.2.2
- name: Detect direct README changes
run: |
if git diff --name-only origin/main | grep -q "README.md"; then
echo "❌ Direct modifications to README.md are not allowed!"
echo "Please update README.template.md instead."
exit 1
fi
+156
View File
@@ -0,0 +1,156 @@
name: Publish Docker Image to Registries
on:
release:
types:
- published # Runs only when a GitHub Release is published
workflow_dispatch: # Allows for manual execution
env:
ALPINE_VERSION: "23-alpine"
DEBIAN_VERSION: "23-slim"
# Registry URLs
DOCKERHUB_REGISTRY: docker.io
GITHUB_REGISTRY: ghcr.io
# Docker Hub image name (using Docker Hub username)
DOCKERHUB_IMAGE_NAME: ${{ secrets.DOCKER_USERNAME }}/${{ github.event.repository.name }}
# GitHub image name (formatted as `account/repo`)
GITHUB_IMAGE_NAME: ${{ github.repository }}
jobs:
check-lockfile:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '23' # Adjust as needed
- name: Install dependencies
run: npm ci
- name: Check for lock-file changes
run: |
git diff --exit-code package-lock.json || (
echo "🫥 package-lock.json is outdated or missing. Please run 'npm install' and commit the updated lockfile."
exit 1
)
build-and-push-to-registries:
needs: check-lockfile # Runs only after `check-lockfile` completes successfully
name: Push Docker images to Docker Hub and GitHub Container Registry
strategy:
matrix:
variant: [alpine, debian]
runs-on: ubuntu-latest
permissions:
actions: write
contents: write
packages: write
# This is used to complete the identity challenge with sigstore/fulcio when running outside of PRs.
id-token: write
steps:
- name: Check out the repo
uses: actions/checkout@v4.2.2
# Install the cosign tool (except on PR)
# https://github.com/sigstore/cosign-installer
- name: Install cosign
if: github.event_name != 'pull_request'
uses: sigstore/cosign-installer@v3.8.0
with:
cosign-release: 'v2.2.4'
# Set up BuildKit Docker container builder to be able to build multi-platform images and export cache
# https://github.com/docker/setup-buildx-action
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.8.0
# Log in to Docker Hub (except on PR)
- name: Log in to Docker Hub
if: github.event_name != 'pull_request'
uses: docker/login-action@v3.3.0
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
# Log in to GitHub Container Registry (except on PR)
- name: Log in to GitHub Container Registry
if: github.event_name != 'pull_request'
uses: docker/login-action@v3.3.0
with:
registry: ${{ env.GITHUB_REGISTRY }}
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
# Combined metadata extraction for both registries
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v5.6.1
with:
images: |
${{ env.DOCKERHUB_IMAGE_NAME }}
${{ env.GITHUB_REGISTRY }}/${{ env.GITHUB_IMAGE_NAME }}
tags: |
# Debian Variant Tags
type=semver,pattern={{version}},enable=${{ matrix.variant == 'debian' }}
type=semver,pattern={{major}}.{{minor}},enable=${{ matrix.variant == 'debian' }}
type=semver,pattern={{major}},enable=${{ matrix.variant == 'debian' }}
type=raw,value=latest,enable=${{ matrix.variant == 'debian' }}
# Alpine Variant Tags
type=semver,pattern={{version}},suffix=-alpine,enable=${{ matrix.variant == 'alpine' }}
type=semver,pattern={{major}}.{{minor}},suffix=-alpine,enable=${{ matrix.variant == 'alpine' }}
type=semver,pattern={{major}},suffix=-alpine,enable=${{ matrix.variant == 'alpine' }}
type=raw,value=alpine,enable=${{ matrix.variant == 'alpine' }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v3.3.0
# Build and push Docker image with Buildx to both registries (don't push on PR)
- name: Build and push Docker image
id: build-and-push
uses: docker/build-push-action@v6.13.0
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
VARIANT=${{ matrix.variant }}
ALPINE_VERSION=${{ env.ALPINE_VERSION }}
DEBIAN_VERSION=${{ env.DEBIAN_VERSION }}
platforms: linux/amd64,linux/arm64
cache-from: type=gha
cache-to: type=gha,mode=max
# Sign the resulting Docker image digests
- name: Sign the published Docker images
if: ${{ github.event_name != 'pull_request' }}
env:
TAGS: ${{ steps.meta.outputs.tags }}
DIGEST: ${{ steps.build-and-push.outputs.digest }}
run: |
echo "${TAGS}" | xargs -I {} cosign sign --yes {}@${DIGEST}
# For use in other workflows (e.g. 'generate-readme', etc.)
- name: Save Build Version to Repository Variable
if: matrix.variant == 'debian' && github.run_attempt == 1
run: |
# VERSION="${{ steps.meta.outputs.version }}"
VERSION=$(gh release view --json tagName -q .tagName 2>/dev/null || echo "")
# Check if VERSION is empty and set a fallback value
if [ -z "$VERSION" ]; then
VERSION=$(git tag -l --sort=-version:refname | grep -E '^(v)?[0-9]+\.[0-9]+\.[0-9]+$' | head -n 1 || echo "3.1.0")
fi
echo "VERSION=$VERSION" >> $GITHUB_ENV
echo "Setting BUILD_VERSION to $VERSION"
gh variable set BUILD_VERSION --body "$VERSION"
env:
GH_TOKEN: ${{ secrets.GH_PAT }} # Needs to be PAT w/ Read access to metadata and secrets & Read and Write access to actions, actions variables, and code
-61
View File
@@ -1,61 +0,0 @@
---
name: Publish Docker image to Dockerhub and GHCR
on:
push:
branches:
- main
tags:
- "*.*.*"
paths-ignore:
- '**/*.md'
- 'docs/**'
jobs:
push_to_registry:
name: Push Docker image to Docker Hub
runs-on: ubuntu-latest
if: ${{ vars.DOCKERHUB_IMAGE_NAME != '' || vars.GHCR_IMAGE_NAME != '' }}
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 != ''
}}
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
with:
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
with:
images: |
${{ vars.DOCKERHUB_IMAGE_NAME }}
${{ vars.GHCR_IMAGE_NAME }}
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:
context: .
push: ${{ github.event_name != 'pull_request' && !env.ACT}}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
platforms: linux/amd64,linux/arm64
+9 -1
View File
@@ -9,6 +9,7 @@ config/server.yaml
/src/lib/.kener
/package
.env
.vscode
.env.*
!.env.example
vite.config.js.timestamp-*
@@ -20,4 +21,11 @@ config/static/*
db/*
!db/.kener
database/*
!database/.kener
!database/.kener
uploads/*
!uploads/upload.dir
static/uploads/*
!static/uploads/upload.dir
temp.txt
temp.js
+69 -7
View File
@@ -1,9 +1,71 @@
{
"useTabs": true,
"semi": true,
"tabWidth": 4,
"trailingComma": "none",
"printWidth": 100,
"plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"],
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
"useTabs": false,
"semi": true,
"tabWidth": 2,
"trailingComma": "none",
"printWidth": 100,
"plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"],
"overrides": [
{
"files": "*.svelte",
"options": {
"parser": "svelte",
"useTabs": false,
"semi": true,
"tabWidth": 2,
"trailingComma": "none",
"printWidth": 120
}
},
{
"files": ["*.js", "*.ts", "*.tsx", "*.cjs", "*.mjs"],
"options": {
"useTabs": false,
"semi": true,
"tabWidth": 2,
"trailingComma": "all",
"printWidth": 120
}
},
{
"files": ["*.json", ".prettierrc"],
"options": {
"useTabs": false,
"semi": false,
"tabWidth": 2,
"trailingComma": "none",
"printWidth": 120
}
},
{
"files": ["*.yaml", "*.yml"],
"options": {
"useTabs": false,
"semi": false,
"tabWidth": 2,
"trailingComma": "none",
"printWidth": 80
}
},
{
"files": "*.md",
"options": {
"useTabs": false,
"semi": false,
"tabWidth": 4,
"trailingComma": "none",
"printWidth": 180
}
},
{
"files": "Dockerfile",
"options": {
"useTabs": true,
"tabWidth": 4,
"semi": false,
"trailingComma": "none",
"printWidth": 120
}
}
]
}
+127 -59
View File
@@ -1,78 +1,146 @@
FROM lsiobase/alpine:3.18 as base
# syntax=docker/dockerfile:1
ENV TZ=Etc/UTC
# Global build arguments (defined default values in case `.env.build` isn't loaded)
ARG ALPINE_VERSION_TAG=23.7.0-alpine3.21
ARG DEBIAN_VERSION_TAG=23.7.0-bookworm-slim
ARG VARIANT=debian
RUN \
echo "**** install build packages ****" && \
apk add --no-cache \
nodejs \
npm \
python3 \
make \
gcc \
g++ \
sqlite \
sqlite-dev \
libc6-compat && \
echo "**** cleanup ****" && \
rm -rf \
/root/.cache \
/tmp/*
#==========================================================#
# STAGE 1: BUILD STAGE #
#==========================================================#
# set OS timezone specified by docker ENV
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
FROM node:${DEBIAN_VERSION_TAG} AS builder-debian
RUN apt-get update && apt-get install -y \
build-essential=12.9 \
python3=3.11.2-1+b1 \
sqlite3=3.40.1-2+deb12u1 \
libsqlite3-dev=3.40.1-2+deb12u1 \
make=4.3-4.1 \
node-gyp=9.3.0-2 \
g++=4:12.2.0-3 \
tzdata \
iputils-ping=3:20221126-1+deb12u1 && \
rm -rf /var/lib/apt/lists/*
# ARG data_dir=/config
# VOLUME $data_dir
# ENV CONFIG_DIR=$data_dir
FROM node:${ALPINE_VERSION_TAG} AS builder-alpine
RUN apk add --no-cache --update \
build-base=0.5-r3 \
python3 \
py3-pip \
make=4.4.1-r2 \
g++=14.2.0-r4 \
sqlite \
sqlite-dev \
tzdata \
iputils
COPY docker/root/ /
FROM builder-${VARIANT} AS builder
# 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
FROM base as build
# Set environment variables
ENV NPM_CONFIG_LOGLEVEL=error \
VITE_BUILD_ENV=production
# Set the working directory
WORKDIR /app
COPY --chown=abc:abc . /app
# Copy package files for dependency installation
COPY package*.json ./
RUN \
npm install node-gyp -g && \
npm_config_build_from_source=true npm install better-sqlite3 && \
npm install \
&& chown -R root:root node_modules \
&& npm run build
# Install all dependencies, including `devDependencies` (cache enabled for faster builds)
# TODO: Possibly add `--no-audit` flag to `npm ci` to prevent `npm` from running a security audit on installed packages. (By default, `npm install` performs an audit to check for vulnerabilities in dependencies, which can slow down installation. Adding this flag would skip the audit, thus making `npm install` significantly faster for the CI/CD pipeline.)
RUN --mount=type=cache,target=/root/.npm \
npm ci --no-fund && \
npm cache clean --force
FROM base as app
# Copy application source code
COPY . .
# copy package, required libs (npm,nodejs) results of build, prod entrypoint, and examples to be used to populate config dir
# to clean, new stage
COPY --chown=abc:abc package*.json ./
COPY --from=base /usr/local/bin /usr/local/bin
COPY --from=base /usr/local/lib /usr/local/lib
# TODO: Reevaluate permissions (possibly reduce?)...
# Remove docs directory and ensure required directories exist
RUN rm -rf src/routes/\(docs\) \
static/documentation \
static/fonts/lato/full && \
mkdir -p uploads database && \
chmod -R 750 uploads database
COPY --chown=abc:abc static /app/static
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 openapi.json /app/openapi.json
COPY --chown=abc:abc src/lib/server /app/src/lib/server
# Build the application and remove `devDependencies`
RUN npm run build && \
npm prune --omit=dev
COPY --from=build --chown=abc:abc /app/build /app/build
COPY --from=build --chown=abc:abc /app/main.js /app/main.js
#==========================================================#
# STAGE 2: PRODUCTION/FINAL STAGE #
#==========================================================#
ENV NODE_ENV=production
FROM node:${DEBIAN_VERSION_TAG} AS final-debian
# TODO: Consider adding `--no-install-recommends`, but will need testing (may further help reduce final build size)
RUN apt-get update && apt-get install -y \
iputils-ping=3:20221126-1+deb12u1 \
sqlite3=3.40.1-2+deb12u1 \
tzdata \
# TODO: Is it ok to change to `curl` here so that we don't have to maintain `wget` version mismatch between Debian architectures? (`curl` is only used for the container healthcheck and because there is an Alpine variant (best!) we probably don't care if the Debian image ends up building bigger due to `curl`.)
curl && \
rm -rf /var/lib/apt/lists/*
# install prod dependencies and clean cache
RUN npm install --omit=dev \
&& npm cache clean --force \
&& chown -R abc:abc node_modules
FROM node:${ALPINE_VERSION_TAG} AS final-alpine
RUN apk add --no-cache --update \
iputils \
sqlite \
tzdata
ARG webPort=3000
ENV PORT=$webPort
FROM final-${VARIANT} AS final
ARG PORT=3000 \
USERNAME=node
# Set environment variables
ENV HEALTHCHECK_PORT=$PORT \
HEALTHCHECK_PATH= \
NODE_ENV=production \
NPM_CONFIG_LOGLEVEL=error \
PORT=$PORT \
TZ=Etc/UTC
# Set the working directory
WORKDIR /app
# Copy package files build artifacts, and necessary files from builder stage
COPY --chown=node:node --from=builder /app/src/lib/ ./src/lib/
COPY --chown=node:node --from=builder /app/build ./build
COPY --chown=node:node --from=builder /app/uploads ./uploads
COPY --chown=node:node --from=builder /app/database ./database
COPY --chown=node:node --from=builder /app/src/lib/server/templates ./build/server/templates
# TODO: Consider changing from copying `node_modules` to instead letting `npm ci --omit=dev` handle production dependencies. Right now, copying `node_modules` is leading to a smaller image, whereas letting `npm ci` handle the install in final image is slightly faster, but leads to larger image size. IMO, having a slightly longer build time (e.g. ~10 sec.) is better in the end to have a smaller image.
COPY --chown=node:node --from=builder /app/node_modules ./node_modules
COPY --chown=node:node --from=builder /app/migrations ./migrations
COPY --chown=node:node --from=builder /app/seeds ./seeds
COPY --chown=node:node --from=builder /app/static ./static
COPY --chown=node:node --from=builder /app/entrypoint.sh ./entrypoint.sh
COPY --chown=node:node --from=builder /app/knexfile.js ./knexfile.js
COPY --chown=node:node --from=builder /app/main.js ./main.js
COPY --chown=node:node --from=builder /app/openapi.json ./openapi.json
COPY --chown=node:node --from=builder /app/openapi.yaml ./openapi.yaml
COPY --chown=node:node --from=builder /app/package.json ./package.json
# Set container timezone and make entrypoint script executable
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone && \
chmod +x ./entrypoint.sh
# TODO: To improve security, consider dropping unnecessary capabilities instead of granting image all network capabilities of host. (Maybe `setcap cap_net_raw+p /usr/bin/ping`, etc.) Could also drop all and then grant only the capabilities that are explicitly needed. Some examples are commented out below...
# setcap cap_net_bind_service=+ep /usr/local/bin/node
# setcap cap_net_bind_service=+ep /usr/bin/ping
# setcap cap_net_bind_service=+ep /usr/bin/ping6
# setcap cap_net_bind_service=+ep /usr/bin/tracepath
# setcap cap_net_bind_service=+ep /usr/bin/clockdiff
# Expose the application port
EXPOSE $PORT
# leave entrypoint blank!
# uses LSIO s6-init entrypoint with scripts
# that populate CONFIG_DIR with static dir, monitor/site.yaml when dir is empty
# and chown's all files so they are owned by proper user based on PUID/GUID env
# Add a healthcheck to the container; `wget` vs. `curl` depending on base image. Using this approach because `wget` does not actually maintain versioning across architectures, so we cannot pin a `wget` version (in above `final-debian` base, `apt-get install`) between differing architectures (e.g. arm64, amd64)
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
CMD sh -c 'if [ -f "/etc/alpine-release" ]; then wget --quiet --spider http://localhost:$HEALTHCHECK_PORT$HEALTHCHECK_PATH || exit 1; else curl --silent --head --fail http://localhost:$HEALTHCHECK_PORT$HEALTHCHECK_PATH || exit 1; fi'
# TODO: Revisit letting user define $PUID & $PGID overrides (e.g. `addgroup -g $PGID newgroup && adduser -D -G newgroup -u $PUID node`) as well as potentially ensure no root user exists. (Make sure no processes are running as root, first!)
# Use a non-root user (recommended for security)
USER $USERNAME
ENTRYPOINT ["/app/entrypoint.sh"]
CMD ["node", "main"]
+166 -64
View File
@@ -1,16 +1,33 @@
# Kener - A Sveltekit NodeJS Status Page System
# Kener - Stunning Status Pages
<p align="center">
<img src="https://kener.ing/newbg.png" width="100%" height="auto" class="rounded-lg shadow-lg" alt="kener example illustration">
<img src="https://kener.ing/newbg.png?v=1" width="100%" height="auto" class="rounded-lg shadow-lg" alt="kener example illustration">
</p>
<p align="center">
<img alt="GitHub Repo stars" src="https://img.shields.io/github/stars/rajnandan1/kener?label=Star%20Repo&style=social">
<a href="https://github.com/ivbeg/awesome-status-pages"><img src="https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg" alt="Awesome status page" /></a>
<a href="https://hub.docker.com/r/rajnandan1/kener"><img src="https://img.shields.io/docker/pulls/rajnandan1/kener" alt="Docker Kener" /></a>
<img alt="GitHub Repo Updated" src="https://badges.pufler.dev/updated/rajnandan1/kener">
<a href="https://awesome-selfhosted.net/tags/status--uptime-pages.html#kener"><img src="https://awesome.re/mentioned-badge.svg" alt="Awesome self hosted" /></a>
</p>
<p align="center">
<a href="https://hub.docker.com/r/rajnandan1/kener"><img src="https://img.shields.io/docker/pulls/rajnandan1/kener" alt="Docker Kener" /></a>
<a href="https://hub.docker.com/r/rajnandan1/kener/tags?page=1&ordering=last_updated&name=latest"><img alt="Docker Image Size" src="https://img.shields.io/docker/image-size/rajnandan1/kener/latest?logo=docker&logoColor=white&label=debian" /></a>
<a href="https://hub.docker.com/r/rajnandan1/kener/tags?page=1&ordering=last_updated&name=alpine"><img alt="Docker Image Size" src="https://img.shields.io/docker/image-size/rajnandan1/kener/alpine?logo=docker&logoColor=white&label=alpine" /></a>
</p>
<p align="center">
<a href="https://github.com/rajnandan1/kener/actions/workflows/publish-images.yml"><img alt="GitHub Workflow Status" src="https://img.shields.io/github/actions/workflow/status/rajnandan1/kener/publish-images.yml" /></a>
<a href="https://github.com/rajnandan1/kener/commit/HEAD"><img src="https://img.shields.io/github/last-commit/rajnandan1/kener/main" alt="" /></a>
<a href="https://github.com/rajnandan1/kener/issues"><img alt="GitHub issues" src="https://img.shields.io/github/issues/rajnandan1/kener.svg" /></a>
</p>
<p align="center">
<a href="https://www.producthunt.com/posts/kener-2" target="_blank">
<img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=kener-2&theme=light" alt="Kener on Product Hunt">
</a>
</p>
<p align="center">
<picture>
<source srcset="https://fonts.gstatic.com/s/e/notoemoji/latest/1f514/512.webp" type="image/webp">
@@ -26,90 +43,175 @@
</picture>
</p>
#### [👉 Visit a live server](https://kener.ing)
#### [👉 Quick Start](https://kener.ing/docs/quick-start)
#### [👉 Documentation](https://kener.ing/docs/home)
| [🌍 Live Server](https://kener.ing) | [🎉 Quick Start](https://kener.ing/docs/quick-start) | [🗄 Documentation](https://kener.ing/docs/home) |
| ----------------------------------- | ---------------------------------------------------- | ----------------------------------------------- |
## What is Kener?
Kener: Open-source sveltekit status page system, crafted with lot of thought so that it looks modern.
**Kener** is a sleek and lightweight status page system built with **SvelteKit** and **NodeJS**. Its not here to replace heavyweights like Datadog or Atlassian but rather to offer a simple, modern, and hassle-free way to set up a great-looking status page with minimal effort.
It does not aim to replace the Datadogs of the world. It simply tries to help someone come with a status page for the world.
Designed with **ease of use** and **customization in mind**, Kener provides all the essential features youd expect from a status page—without unnecessary complexity.
### Why Kener?
✅ &nbsp;Minimal overhead &ndash; Set up quickly with a clean, modern UI<br>
✅ &nbsp;Customizable &ndash; Easily tailor it to match your brand<br>
✅ &nbsp;Open-source & free &ndash; Because great tools should be accessible to everyone
### What's in a Name?
“Kener” is inspired by the Assamese word _“Kene”_, meaning _“hows it going?”_. The _.ing_ was added because, well… that domain was available. 😄
## Installation
### Manual
```shell
# Clone the repository
git clone https://github.com/rajnandan1/kener.git
cd kener
npm install
cp .env.example .env
npm run dev
```
### Docker
Official Docker images for **Kener** are available on [Docker Hub](https://hub.docker.com/r/rajnandan1/kener). Multiple versions are maintained to support different use cases.
<a href="https://hub.docker.com/r/rajnandan1/kener/tags?page=1&ordering=last_updated&name=3.2.17"><img src="https://img.shields.io/badge/Latest_Stable_Release-3.2.17-blue" alt="Kener latest stable version: 3.2.17" /></a>
#### Available Tags
<table>
<tr>
<th>Image Tag</th>
<th>Description</th>
</tr>
<tr>
<td align="left" colspan="2" style="color:#A81D33;text-align:left;">Debian 12 <small>(Bookwork Slim)</small> w/ Node.js v23.7.0 &nbsp;<strong><em>(default)</em></strong></td>
</tr>
<tr>
<td><a href="https://hub.docker.com/r/rajnandan1/kener/tags?page=1&ordering=last_updated&name=latest" target="_blank"><code>latest</code></td>
<td>Latest stable release (aka 3.2.17)</td>
</tr>
<tr>
<td><a href="https://hub.docker.com/r/rajnandan1/kener/tags?page=1&ordering=last_updated&name=3.2.17" target="_blank"><code>3.2.17</code></a></td>
<td>Specific release version</td>
</tr>
<tr>
<td><a href="https://hub.docker.com/r/rajnandan1/kener/tags?page=1&ordering=last_updated&name=3.2" target="_blank"><code>3.2</code></a></td>
<td>Major-minor version tag pointing to the latest patch (3.2.17) release within that minor version (3.2.x)</td>
</tr>
<tr>
<td><a href="https://hub.docker.com/r/rajnandan1/kener/tags?page=1&ordering=last_updated&name=3" target="_blank"><code>3</code></a></td>
<td>Major version tag pointing to the latest stable (3.2.17) release within that major version (3.x.x)</td>
</tr>
<tr>
<td align="left" colspan="2" style="color:#0D597F;text-align:left;">Alpine Linux 3.21 w/ Node.js v23.7.0 &nbsp;<strong><em>(smallest image size)</em></strong></td>
</tr>
<tr>
<td><a href="https://hub.docker.com/r/rajnandan1/kener/tags?page=1&ordering=last_updated&name=alpine" target="_blank"><code>alpine</code></td>
<td>Latest stable release (aka 3.2.17)</td>
</tr>
<tr>
<td><a href="https://hub.docker.com/r/rajnandan1/kener/tags?page=1&ordering=last_updated&name=3.2.17-alpine" target="_blank"><code>3.2.17-alpine</code></a></td>
<td>Specific release version</td>
</tr>
<tr>
<td><a href="https://hub.docker.com/r/rajnandan1/kener/tags?page=1&ordering=last_updated&name=3.2-alpine" target="_blank"><code>3.2-alpine</code></a></td>
<td>Major-minor version tag pointing to the latest patch (3.2.17) release within that minor version (3.2.x)</td>
</tr>
<tr>
<td><a href="https://hub.docker.com/r/rajnandan1/kener/tags?page=1&ordering=last_updated&name=3-alpine" target="_blank"><code>3-alpine</code></a></td>
<td>Major version tag pointing to the latest stable (3.2.17) release within that major version (3.x.x)</td>
</tr>
</table>
#### Usage
Pull the latest stable version:
```sh
docker pull rajnandan1/kener:latest
```
Or use the smaller, Alpine-based variant:
```sh
docker pull rajnandan1/kener:alpine
```
For a production setup, refer to the sample [docker-compose.yml](https://github.com/rajnandan1/kener/blob/main/docker-compose.yml).
This keeps things clean, structured, and easy to read while preserving all the details.
### One Click
[![Deploy on Railway](https://railway.com/button.svg)](https://railway.com/template/spSvic?referralCode=1Pn7vs)
Kener name is derived from the word "Kene" which means "how is it going" in Assamese, then .ing is added to make cooler.
<div align="left">
<img alt="Visitor Stats" src="https://widgetbite.com/stats/rajnandan"/>
</div>
## Features
Here are some of the features that you get out of the box. Please read the documentation to know how to use them.
### Monitoring and Tracking
### 📊 &nbsp;Monitoring and Tracking
- Real-time monitoring
- Polls HTTP endpoint or Push data to monitor using Rest APIs
- Handles Timezones for visitors
- Categorize Monitors into different Sections
- 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 don't hit API per minute with Status UP
- Supports base path for hosting in k8s
- Pre-built docker image for easy deployment
- Supports webhooks/discord/slack for notifications
- Advanced **application performance monitoring** tools
- **Real-time network monitoring** capabilities
- Supports **polling HTTP endpoints** or **pushing data** via REST APIs
- **Timezone auto-adjustment** for visitors
- Organize monitors into **custom sections**
- **Cron-based scheduling** (minimum: **every minute**)
- **Create complex API polls** (chaining, secrets, etc.)
- Set a **default status** for monitors
- Supports **base path hosting in Kubernetes (k8s)**
- **Pre-built Docker images** for easy deployment
### Customization and Branding
### 🎨 &nbsp;Customization and Branding
- Customizable status page using yaml or code
- Badge generation for status and uptime of Monitors
- Support for custom domains
- Embed Monitor as an iframe or widget
- Light + Dark Theme
- Internationalization support
- Fully **customizable status page**
- **Badge generation** for status and uptime tracking
- Support for **custom domains**
- Embed monitors as **iframes or widgets**
- **Light & Dark Mode**
- **Internationalization (i18n) support**
- **Sleek, beautifully crafted UI**
### Incident Management
### 🚨 &nbsp;Incident Management
- Create Incidents using Github Issues - Rich Text
- Or use APIs to create Incidents
- **Incident tracking & communication** tools
- **Comprehensive APIs** for incident management
### User Experience and Design
### 🧑‍💻 &nbsp;User Experience and Design
- 100% Accessibility Score
- Easy installation and setup
- User-friendly interface
- Responsive design for various devices
- Auto SEO and Social Media ready
- **Accessible & user-friendly interface**
- **Quick & easy installation**
- **Responsive design** for all devices
- **Auto SEO & Social Media ready**
- **Server-Side Rendering (SSR) for better performance**
## Technologies used
<div align="left">
<img alt="Visitor Stats" src="https://widgetbite.com/stats/rajnandan"/>
</div>
## Technologies Used
- [SvelteKit](https://kit.svelte.dev/)
- [shadcn-svelte](https://www.shadcn-svelte.com/)
## Inspired from
- [Upptime](https://upptime.js.org/)
## Screenshots
![image](static/marken_90.png)
![image](static/marken_api.png)
![image](static/marken_badge.png)
![image](static/marken_embed.png)
![image](static/marken_inci.png)
![image](static/marken_share.png)
![image](static/marken_td.png)
![image](static/marken_tl.png)
![image](static/marken_theme.png)
## Support Me
If you are using Kener and want to support me, you can do so by sponsoring me on GitHub or buying me a coffee.
If youre enjoying Kener and want to support its development, consider sponsoring me on GitHub or treating me to a coffee. Your support helps keep the project growing! 🚀
[Sponsor Me Using Github](https://github.com/sponsors/rajnandan1)
[Buy Me a Coffee](https://www.buymeacoffee.com/rajnandan1)
☕ &nbsp;[Buy Me a Coffee](https://www.buymeacoffee.com/rajnandan1)
![image](https://badges.pufler.dev/visits/rajnandan1/kener)
## Contributing
If you want to contribute to Kener, please read the [Contribution Guide](https://github.com/rajnandan1/kener/blob/main/.github/CONTRIBUTING.md).
## Star History
[![Star History Chart](https://api.star-history.com/svg?repos=rajnandan1/kener&type=Date)](https://star-history.com/#rajnandan1/kener&Date)
+217
View File
@@ -0,0 +1,217 @@
# Kener - Stunning Status Pages
<p align="center">
<img src="https://kener.ing/newbg.png?v=1" width="100%" height="auto" class="rounded-lg shadow-lg" alt="kener example illustration">
</p>
<p align="center">
<img alt="GitHub Repo stars" src="https://img.shields.io/github/stars/rajnandan1/kener?label=Star%20Repo&style=social">
<a href="https://github.com/ivbeg/awesome-status-pages"><img src="https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg" alt="Awesome status page" /></a>
<a href="https://awesome-selfhosted.net/tags/status--uptime-pages.html#kener"><img src="https://awesome.re/mentioned-badge.svg" alt="Awesome self hosted" /></a>
</p>
<p align="center">
<a href="https://hub.docker.com/r/rajnandan1/kener"><img src="https://img.shields.io/docker/pulls/rajnandan1/kener" alt="Docker Kener" /></a>
<a href="https://hub.docker.com/r/rajnandan1/kener/tags?page=1&ordering=last_updated&name=latest"><img alt="Docker Image Size" src="https://img.shields.io/docker/image-size/rajnandan1/kener/latest?logo=docker&logoColor=white&label=debian" /></a>
<a href="https://hub.docker.com/r/rajnandan1/kener/tags?page=1&ordering=last_updated&name=alpine"><img alt="Docker Image Size" src="https://img.shields.io/docker/image-size/rajnandan1/kener/alpine?logo=docker&logoColor=white&label=alpine" /></a>
</p>
<p align="center">
<a href="https://github.com/rajnandan1/kener/actions/workflows/publish-images.yml"><img alt="GitHub Workflow Status" src="https://img.shields.io/github/actions/workflow/status/rajnandan1/kener/publish-images.yml" /></a>
<a href="https://github.com/rajnandan1/kener/commit/HEAD"><img src="https://img.shields.io/github/last-commit/rajnandan1/kener/main" alt="" /></a>
<a href="https://github.com/rajnandan1/kener/issues"><img alt="GitHub issues" src="https://img.shields.io/github/issues/rajnandan1/kener.svg" /></a>
</p>
<p align="center">
<a href="https://www.producthunt.com/posts/kener-2" target="_blank">
<img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=kener-2&theme=light" alt="Kener on Product Hunt">
</a>
</p>
<p align="center">
<picture>
<source srcset="https://fonts.gstatic.com/s/e/notoemoji/latest/1f514/512.webp" type="image/webp">
<img src="https://fonts.gstatic.com/s/e/notoemoji/latest/1f514/512.gif" alt="🔔" width="32" height="32">
</picture>
<picture>
<source srcset="https://fonts.gstatic.com/s/e/notoemoji/latest/1f680/512.webp" type="image/webp">
<img src="https://fonts.gstatic.com/s/e/notoemoji/latest/1f680/512.gif" alt="🚀" width="32" height="32">
</picture>
<picture>
<source srcset="https://fonts.gstatic.com/s/e/notoemoji/latest/1f6a7/512.webp" type="image/webp">
<img src="https://fonts.gstatic.com/s/e/notoemoji/latest/1f6a7/512.gif" alt="🚧" width="32" height="32">
</picture>
</p>
| [🌍 Live Server](https://kener.ing) | [🎉 Quick Start](https://kener.ing/docs/quick-start) | [🗄 Documentation](https://kener.ing/docs/home) |
| ----------------------------------- | ---------------------------------------------------- | ----------------------------------------------- |
## What is Kener?
**Kener** is a sleek and lightweight status page system built with **SvelteKit** and **NodeJS**. Its not here to replace heavyweights like Datadog or Atlassian but rather to offer a simple, modern, and hassle-free way to set up a great-looking status page with minimal effort.
Designed with **ease of use** and **customization in mind**, Kener provides all the essential features youd expect from a status page—without unnecessary complexity.
### Why Kener?
✅ &nbsp;Minimal overhead &ndash; Set up quickly with a clean, modern UI<br>
✅ &nbsp;Customizable &ndash; Easily tailor it to match your brand<br>
✅ &nbsp;Open-source & free &ndash; Because great tools should be accessible to everyone
### What's in a Name?
“Kener” is inspired by the Assamese word _“Kene”_, meaning _“hows it going?”_. The _.ing_ was added because, well… that domain was available. 😄
## Installation
### Manual
```shell
# Clone the repository
git clone https://github.com/rajnandan1/kener.git
cd kener
npm install
cp .env.example .env
npm run dev
```
### Docker
Official Docker images for **Kener** are available on [Docker Hub](https://hub.docker.com/r/rajnandan1/kener). Multiple versions are maintained to support different use cases.
<a href="https://hub.docker.com/r/rajnandan1/kener/tags?page=1&ordering=last_updated&name={{kener_full_version}}"><img src="https://img.shields.io/badge/Latest_Stable_Release-{{kener_full_version}}-blue" alt="Kener latest stable version: {{kener_full_version}}" /></a>
#### Available Tags
<table>
<tr>
<th>Image Tag</th>
<th>Description</th>
</tr>
<tr>
<td align="left" colspan="2" style="color:#A81D33;text-align:left;">Debian 12 <small>(Bookwork Slim)</small> w/ Node.js v23.7.0 &nbsp;<strong><em>(default)</em></strong></td>
</tr>
<tr>
<td><a href="https://hub.docker.com/r/rajnandan1/kener/tags?page=1&ordering=last_updated&name=latest" target="_blank"><code>latest</code></td>
<td>Latest stable release (aka {{kener_full_version}})</td>
</tr>
<tr>
<td><a href="https://hub.docker.com/r/rajnandan1/kener/tags?page=1&ordering=last_updated&name={{kener_full_version}}" target="_blank"><code>{{kener_full_version}}</code></a></td>
<td>Specific release version</td>
</tr>
<tr>
<td><a href="https://hub.docker.com/r/rajnandan1/kener/tags?page=1&ordering=last_updated&name={{kener_major_minor_version}}" target="_blank"><code>{{kener_major_minor_version}}</code></a></td>
<td>Major-minor version tag pointing to the latest patch ({{kener_full_version}}) release within that minor version ({{kener_major_minor_version}}.x)</td>
</tr>
<tr>
<td><a href="https://hub.docker.com/r/rajnandan1/kener/tags?page=1&ordering=last_updated&name={{kener_major_version}}" target="_blank"><code>{{kener_major_version}}</code></a></td>
<td>Major version tag pointing to the latest stable ({{kener_full_version}}) release within that major version ({{kener_major_version}}.x.x)</td>
</tr>
<tr>
<td align="left" colspan="2" style="color:#0D597F;text-align:left;">Alpine Linux 3.21 w/ Node.js v23.7.0 &nbsp;<strong><em>(smallest image size)</em></strong></td>
</tr>
<tr>
<td><a href="https://hub.docker.com/r/rajnandan1/kener/tags?page=1&ordering=last_updated&name=alpine" target="_blank"><code>alpine</code></td>
<td>Latest stable release (aka {{kener_full_version}})</td>
</tr>
<tr>
<td><a href="https://hub.docker.com/r/rajnandan1/kener/tags?page=1&ordering=last_updated&name={{kener_full_version}}-alpine" target="_blank"><code>{{kener_full_version}}-alpine</code></a></td>
<td>Specific release version</td>
</tr>
<tr>
<td><a href="https://hub.docker.com/r/rajnandan1/kener/tags?page=1&ordering=last_updated&name={{kener_major_minor_version}}-alpine" target="_blank"><code>{{kener_major_minor_version}}-alpine</code></a></td>
<td>Major-minor version tag pointing to the latest patch ({{kener_full_version}}) release within that minor version ({{kener_major_minor_version}}.x)</td>
</tr>
<tr>
<td><a href="https://hub.docker.com/r/rajnandan1/kener/tags?page=1&ordering=last_updated&name={{kener_major_version}}-alpine" target="_blank"><code>{{kener_major_version}}-alpine</code></a></td>
<td>Major version tag pointing to the latest stable ({{kener_full_version}}) release within that major version ({{kener_major_version}}.x.x)</td>
</tr>
</table>
#### Usage
Pull the latest stable version:
```sh
docker pull rajnandan1/kener:latest
```
Or use the smaller, Alpine-based variant:
```sh
docker pull rajnandan1/kener:alpine
```
For a production setup, refer to the sample [docker-compose.yml](https://github.com/rajnandan1/kener/blob/main/docker-compose.yml).
This keeps things clean, structured, and easy to read while preserving all the details.
### One Click
[![Deploy on Railway](https://railway.com/button.svg)](https://railway.com/template/spSvic?referralCode=1Pn7vs)
## Features
Here are some of the features that you get out of the box. Please read the documentation to know how to use them.
### 📊 &nbsp;Monitoring and Tracking
- Advanced **application performance monitoring** tools
- **Real-time network monitoring** capabilities
- Supports **polling HTTP endpoints** or **pushing data** via REST APIs
- **Timezone auto-adjustment** for visitors
- Organize monitors into **custom sections**
- **Cron-based scheduling** (minimum: **every minute**)
- **Create complex API polls** (chaining, secrets, etc.)
- Set a **default status** for monitors
- Supports **base path hosting in Kubernetes (k8s)**
- **Pre-built Docker images** for easy deployment
### 🎨 &nbsp;Customization and Branding
- Fully **customizable status page**
- **Badge generation** for status and uptime tracking
- Support for **custom domains**
- Embed monitors as **iframes or widgets**
- **Light & Dark Mode**
- **Internationalization (i18n) support**
- **Sleek, beautifully crafted UI**
### 🚨 &nbsp;Incident Management
- **Incident tracking & communication** tools
- **Comprehensive APIs** for incident management
### 🧑‍💻 &nbsp;User Experience and Design
- **Accessible & user-friendly interface**
- **Quick & easy installation**
- **Responsive design** for all devices
- **Auto SEO & Social Media ready**
- **Server-Side Rendering (SSR) for better performance**
<div align="left">
<img alt="Visitor Stats" src="https://widgetbite.com/stats/rajnandan"/>
</div>
## Technologies Used
- [SvelteKit](https://kit.svelte.dev/)
- [shadcn-svelte](https://www.shadcn-svelte.com/)
## Support Me
If youre enjoying Kener and want to support its development, consider sponsoring me on GitHub or treating me to a coffee. Your support helps keep the project growing! 🚀
[Sponsor Me Using Github](https://github.com/sponsors/rajnandan1)
☕ &nbsp;[Buy Me a Coffee](https://www.buymeacoffee.com/rajnandan1)
![image](https://badges.pufler.dev/visits/rajnandan1/kener)
## Contributing
If you want to contribute to Kener, please read the [Contribution Guide](https://github.com/rajnandan1/kener/blob/main/.github/CONTRIBUTING.md).
## Star History
[![Star History Chart](https://api.star-history.com/svg?repos=rajnandan1/kener&type=Date)](https://star-history.com/#rajnandan1/kener&Date)
-460
View File
@@ -1,460 +0,0 @@
import yaml from "js-yaml";
import fs from "fs-extra";
import axios from "axios";
import {
IsValidURL,
checkIfDuplicateExists,
IsValidHTTPMethod,
ValidateIpAddress,
IsValidHost,
IsValidRecordType,
IsValidNameServer
} from "./src/lib/server/tool.js";
import { API_TIMEOUT, AnalyticsProviders } from "./src/lib/server/constants.js";
import { GetAllGHLabels, CreateGHLabel } from "./src/lib/server/github.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)) {
return {
status: 'UP',
latency: responseTime,
}
}
return {
status: 'DOWN',
latency: responseTime,
}
})`;
function validateServerFile(server) {
//if empty return true
if (Object.keys(server).length === 0) {
return true;
}
//server.triggers is present then it should be an array
if (server.triggers !== undefined && !Array.isArray(server.triggers)) {
console.log("triggers should be an array");
return false;
}
///each trigger should have a name, type, and url
if (server.triggers !== undefined) {
for (let i = 0; i < server.triggers.length; i++) {
const trigger = server.triggers[i];
if (
trigger.name === undefined ||
trigger.type === undefined ||
trigger.url === undefined
) {
console.log("trigger should have name, type, and url");
return false;
}
}
}
//if database is present then it should be an object, and they key can be either postgres or sqlite
if (server.database !== undefined && typeof server.database !== "object") {
console.log("database should be an object");
return false;
}
if (server.database !== undefined) {
let dbtype = Object.keys(server.database);
if (dbtype.length !== 1) {
console.log("database should have only one key");
return false;
}
if (dbtype[0] !== "postgres" && dbtype[0] !== "sqlite") {
console.log("database should be either postgres or sqlite");
return false;
}
}
return true;
}
async function Build() {
console.log("️ Building Kener...");
let site = {};
let server = {};
let monitors = [];
try {
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);
}
try {
server = yaml.load(fs.readFileSync(configPathFolder + "/server.yaml", "utf8"));
if (!validateServerFile(server)) {
process.exit(1);
}
} catch (error) {
console.warn("server.yaml not found");
server = {};
}
if (
site.github === undefined ||
site.github.owner === undefined ||
site.github.repo === undefined
) {
console.log("github owner and repo are required");
site.hasGithub = false;
// process.exit(1);
} else {
site.hasGithub = true;
}
if (site.hasGithub && !!!site.github.incidentSince) {
site.github.incidentSince = 720;
}
if (site.hasGithub && !!!site.github.apiURL) {
site.github.apiURL = "https://api.github.com";
}
const FOLDER_DB = databaseFolder;
const FOLDER_SITE = FOLDER_DB + "/site.json";
const FOLDER_MONITOR = FOLDER_DB + "/monitors.json";
const FOLDER_SERVER = FOLDER_DB + "/server.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;
let hasPing = monitor.ping !== undefined && monitor.ping !== null;
let hasDNS = monitor.dns !== undefined && monitor.dns !== null;
let folderName = name.replace(/[^a-z0-9]/gi, "-").toLowerCase();
monitors[i].folderName = folderName;
if (!name || !tag) {
console.log("name, tag are required");
process.exit(1);
}
if (
monitor.dayDegradedMinimumCount &&
(isNaN(monitor.dayDegradedMinimumCount) || monitor.dayDegradedMinimumCount < 1)
) {
console.log("dayDegradedMinimumCount is not a number or it is less than 1");
process.exit(1);
} else if (monitor.dayDegradedMinimumCount === undefined) {
monitors[i].dayDegradedMinimumCount = 1;
}
if (
monitor.dayDownMinimumCount &&
(isNaN(monitor.dayDownMinimumCount) || monitor.dayDownMinimumCount < 1)
) {
console.log("dayDownMinimumCount is not a number or it is less than 1");
process.exit(1);
} else if (monitor.dayDownMinimumCount === undefined) {
monitors[i].dayDownMinimumCount = 1;
}
if (
monitor.includeDegradedInDowntime === undefined ||
monitor.includeDegradedInDowntime !== true
) {
monitors[i].includeDegradedInDowntime = false;
}
if (hasPing) {
let hostsV4 = monitor.ping.hostsV4;
let hostsV6 = monitor.ping.hostsV6;
let hasV4 = false;
let hasV6 = false;
if (hostsV4 && Array.isArray(hostsV4) && hostsV4.length > 0) {
hostsV4.forEach((host) => {
if (ValidateIpAddress(host) == "Invalid") {
console.log(`hostsV4 ${host} is not valid`);
process.exit(1);
}
});
hasV4 = true;
}
if (hostsV6 && Array.isArray(hostsV6) && hostsV6.length > 0) {
hostsV6.forEach((host) => {
if (ValidateIpAddress(host) == "Invalid") {
console.log(`hostsV6 ${host} is not valid`);
process.exit(1);
}
});
hasV6 = true;
}
if (!hasV4 && !hasV6) {
console.log("hostsV4 or hostsV6 is required");
process.exit(1);
}
monitors[i].hasPing = true;
}
if (hasDNS) {
let dnsData = monitor.dns;
let domain = dnsData.host;
//check if domain is valid
if (!!!domain || !IsValidHost(domain)) {
console.log("domain is not valid");
process.exit(1);
}
let recordType = dnsData.lookupRecord;
//check if recordType is valid
if (!!!recordType || !IsValidRecordType(recordType)) {
console.log("recordType is not valid");
process.exit(1);
}
let nameServer = dnsData.nameServer;
//check if nameserver is valid
if (!!nameServer && !IsValidNameServer(nameServer)) {
console.log("nameServer is not valid");
process.exit(1);
}
// matchType: "ANY" # ANY, ALL
let matchType = dnsData.matchType;
if (!!!matchType || (matchType !== "ANY" && matchType !== "ALL")) {
console.log("matchType is not valid");
process.exit(1);
}
//values array of string at least one
let values = dnsData.values;
if (!!!values || !Array.isArray(values) || values.length === 0) {
console.log("values is not valid");
process.exit(1);
}
monitors[i].hasDNS = true;
}
if (hasAPI) {
let url = monitor.api.url;
let method = monitor.api.method;
let headers = monitor.api.headers;
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)) {
console.log("url is not valid");
process.exit(1);
}
}
if (!!method) {
if (!IsValidHTTPMethod(method)) {
console.log("method is not valid");
process.exit(1);
}
method = method.toUpperCase();
} else {
method = "GET";
}
monitors[i].api.method = method;
//headers
if (headers === undefined || headers === null) {
monitors[i].api.headers = undefined;
} else {
//check if headers is a valid json
try {
JSON.parse(JSON.stringify(headers));
} catch (error) {
console.log("headers are not valid. Quitting");
process.exit(1);
}
}
//eval
if (evaluator === undefined || evaluator === null) {
monitors[i].api.eval = defaultEval;
} else {
let evalResp = eval(evaluator + `(200, 1000, "e30=")`);
if (
evalResp === undefined ||
evalResp === null ||
evalResp.status === undefined ||
evalResp.status === null ||
evalResp.latency === undefined ||
evalResp.latency === null
) {
console.log("eval is not valid ");
process.exit(1);
}
monitors[i].api.eval = evaluator;
}
//body
if (body === undefined || body === null) {
monitors[i].api.body = undefined;
} else {
//check if body is a valid string
if (typeof body !== "string") {
console.log("body is not valid should be a string");
process.exit(1);
}
}
//timeout
if (timeout === undefined || timeout === null) {
monitors[i].api.timeout = API_TIMEOUT;
} else {
//check if timeout is a valid number
if (isNaN(timeout) || timeout < 0) {
console.log("timeout is not valid ");
process.exit(1);
}
}
//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 received content-type is text/html
//if yes, append to description
if (
!hideURLForGet &&
(headers === undefined || headers === null) &&
url !== undefined &&
method === "GET"
) {
try {
const response = await axios({
method: "GET",
url: url,
timeout: API_TIMEOUT
});
if (response.headers["content-type"].includes("text/html")) {
let link = `<a href="${url}" class="font-medium underline underline-offset-4" target="_blank">${url}</a>`;
if (monitors[i].description === undefined) {
monitors[i].description = link;
} else {
monitors[i].description = monitors[i].description?.trim() + " " + link;
}
}
} catch (error) {
console.log(`error while fetching ${url}`);
}
}
}
monitors[i].hasAPI = hasAPI;
}
if (site.siteName === undefined) {
site.siteName = site.title;
}
if (site.theme === undefined) {
site.theme = "system";
}
if (site.themeToggle === undefined) {
site.themeToggle = true;
}
if (site.barStyle === undefined) {
site.barStyle = "FULL";
}
if (site.barRoundness === undefined) {
site.barRoundness = "ROUNDED";
} else {
site.barRoundness = site.barRoundness.toLowerCase();
}
if (site.summaryStyle === undefined) {
site.summaryStyle = "DAY";
}
site.colors = {
UP: site.colors?.UP || "#4ead94",
DOWN: site.colors?.DOWN || "#ca3038",
DEGRADED: site.colors?.DEGRADED || "#e6ca61"
};
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");
process.exit(1);
}
if (checkIfDuplicateExists(monitors.map((monitor) => monitor.tag)) === true) {
console.log("duplicate tag detected");
process.exit(1);
}
fs.ensureFileSync(FOLDER_MONITOR);
fs.ensureFileSync(FOLDER_SITE);
fs.ensureFileSync(FOLDER_SERVER);
try {
fs.writeFileSync(FOLDER_MONITOR, JSON.stringify(monitors, null, 4));
fs.writeFileSync(FOLDER_SITE, JSON.stringify(site, null, 4));
fs.writeFileSync(FOLDER_SERVER, JSON.stringify(server, null, 4));
} catch (error) {
console.log(error);
process.exit(1);
}
console.log("✅ Kener built successfully");
if (site.hasGithub) {
const ghLabels = await GetAllGHLabels(site);
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(site, "incident", "Status of the site");
}
if (ghLabels.indexOf("resolved") === -1) {
await CreateGHLabel(site, "resolved", "Incident is resolved", "65dba6");
}
if (ghLabels.indexOf("identified") === -1) {
await CreateGHLabel(site, "identified", "Incident is Identified", "EBE3D5");
}
if (ghLabels.indexOf("manual") === -1) {
await CreateGHLabel(site, "manual", "Manually Created Incident", "6499E9");
}
if (ghLabels.indexOf("auto") === -1) {
await CreateGHLabel(site, "auto", "Automatically Created Incident", "D6C0B3");
}
if (ghLabels.indexOf("investigating") === -1) {
await CreateGHLabel(site, "investigating", "Incident is investigated", "D4E2D4");
}
if (ghLabels.indexOf("incident-degraded") === -1) {
await CreateGHLabel(
site,
"incident-degraded",
"Status is degraded of the site",
"f5ba60"
);
}
if (ghLabels.indexOf("incident-down") === -1) {
await CreateGHLabel(site, "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(site, tag, description);
}
}
console.log("✅ Github labels created successfully");
}
}
Build();
-48
View File
@@ -1,48 +0,0 @@
- 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"
- 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
alerts:
DOWN:
failureThreshold: 5
successThreshold: 2
createIncident: false
description: "Write a description here please"
triggers:
- MyWebhook
- Discord Test
- name: CNAME Lookup
description: Monitor example showing how to lookup CNAME record for a domain. The site www.rajnandan.com is hosted on GitHub Pages.
tag: "cname-rajnandan"
image: "https://www.rajnandan.com/assets/images/me.jpg"
defaultStatus: "UP"
dns:
host: "www.rajnandan.com"
lookupRecord: "CNAME"
nameServer: "8.8.8.8"
matchType: "ANY" # ANY, ALL
values:
- "rajnandan1.github.io"
- name: "Frogment APP Ping"
description: "Ping www.frogment.app"
image: https://www.frogment.app/icons/Square107x107Logo.png
tag: "pingFrogmentApp"
defaultStatus: "UP"
ping:
hostsV4:
- "www.frogment.app"
-13
View File
@@ -1,13 +0,0 @@
triggers:
- name: MyWebhook
type: webhook
url: https://kener.requestcatcher.com/test
method: POST
headers:
Authorization: Bearer SomeToken
- name: Discord Test
type: discord
url: https://discord.com/api/webhooks/1310641119767302164/XJvq4MO2lz5yp9XRCJgfc4dbUfcQdHsttFUKTFJx4y_Oo1jNkUXf-CS3RSnamnNNv4Lx
database:
sqlite:
dbName: kener.db
-58
View File
@@ -1,58 +0,0 @@
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: 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"
og: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."
og:image: "https://kener.ing/ss.png"
og:title: "Kener - Open-Source and Modern looking Node.js Status Page for Effortless Incident Management"
og:type: "website"
og:site_name: "Kener"
twitter:card: "summary_large_image"
twitter:site: "@_rajnandan_"
twitter:creator: "@_rajnandan_"
twitter:image: "https://kener.ing/ss.png"
twitter:title: "Kener: Open-Source and Modern looking Node.js Status Page for Effortless Incident Management"
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/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 an Modern Open-Source Status Page System
subtitle: Let your users know what's going on.
footerHTML: |
Made using
<a href="https://github.com/rajnandan1/kener" target="_blank" rel="noreferrer" class="font-medium underline underline-offset-4">
Kener
</a>
an open source status page system built with Svelte and TailwindCSS.
i18n:
defaultLocale: "en"
locales:
en: "English"
hi: "हिन्दी"
zh-CN: "中文"
ja: "日本語"
vi: "Tiếng Việt"
pattern: "squares"
analytics:
- id: "G-Q3MLRXCBFT"
type: "GA"
barRoundness: SHARP
summaryStyle: CURRENT
barStyle: PARTIAL
-1
View File
@@ -1 +0,0 @@
.kener
-40
View File
@@ -1,40 +0,0 @@
import fs from "fs-extra";
import path from "path";
let maxWait = 5000;
let interval = 1000;
let waitTime = 0;
let serverDataPath = path.join(process.cwd(), "database", "server.json");
let siteDataPath = path.join(process.cwd(), "database", "site.json");
let monitorsDataPath = path.join(process.cwd(), "database", "monitors.json");
function allFilesExist() {
return (
fs.existsSync(serverDataPath) &&
fs.existsSync(siteDataPath) &&
fs.existsSync(monitorsDataPath)
);
}
//use setTimeout to create a delay promise
function delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
let requiredFilesExist = allFilesExist();
//create anonymous function to call the init function
(async function init() {
while (!requiredFilesExist && waitTime < maxWait) {
await delay(1000);
requiredFilesExist = allFilesExist();
waitTime += interval;
}
if (!requiredFilesExist) {
console.error("Error loading site data");
process.exit(1);
} else {
console.log("✅ All files exist. Starting Frontend server...");
}
})();
+52 -18
View File
@@ -1,27 +1,61 @@
version: '3.7'
# Docker Compose Configuration
# Description: This file sets up a multi-container environment for Kener (https://github.com/rajnandan1/kener).
# Last Updated: 2025-02-08
# Docker Compose Version: 3.8
# Notes: Ensure that you specify a random value for the `KENER_SECRET_KEY` environment variable before running `docker-compose up -d`.
version: '3.8'
services:
kener:
image: rajnandan1/kener:latest
image: rajnandan1/kener:latest # Change to 'rajnandan1/kener:alpine' for an even smaller image! 😁🚀
container_name: kener
#env_file: .env #uncomment this, if you are using .env file
# env_file: custom.env # Uncomment this if you are needing to export environment variables from a custom environment file. By default, Docker will import any variables that exist in `.env`
environment:
- TZ=Etc/UTC
#- GH_TOKEN=
#- API_TOKEN=
#- API_IP=
#- API_IP_REGEX=
#- KENER_BASE_PATH=
TZ: Etc/UTC
KENER_SECRET_KEY: replace_me_with_a_random_string # Keep private!! - best to define in `.env` file or through Docker Secret
# DATABASE_URL: custom_db_url # By default, a SQLite database is used - you may override the database url/type here
# RESEND_API_KEY:
# RESEND_SENDER_EMAIL:
# 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
#- PUID=1000 # gid
#- PGID=1000 # uid
### You most likely will NOT need to change anything below this line. Be sure you know what you're doing!! (https://kener.ing/docs/deployment/#docker-environment-variables)
### Most likely DO NOT need to change anything below this ###
#- PORT=3000 Port app listens on IN CONTAINER
# PORT: 3000 # Port that app listens on in the container
# KENER_BASE_PATH: # By default, Kener runs at `/`. You may change this to be, e.g. `/status`, etc. Do NOT add a trailing slash!! (more info here: https://kener.ing/docs/deployment/#docker-environment-variables)
# ORIGIN: http://localhost:3000
# NODE_ENV: production # This is already set to "production" by default within the container
ports:
- '3000:3000/tcp'
volumes:
- './database:/app/database:rw'
- './config:/app/config'
- data:/app/database # We suggest using a Docker named volume, which is more performant for databases
- $(pwd)/uploads:/app/uploads
# read_only: true # Uncommenting this fortifies security by marking the container's filesystem as read-only (aka no data can be written to the container's filesystem except for explicitly defined writable volumes and bind mounts, an exception has already been defined for `/database` and `/uploads`)
restart: unless-stopped
# depends_on: # <-- Uncomment if you would like to use PostgreSQL or MySQL
# - postgres # ...instead of SQLite
# - mysql #
# Only use below section if you would like to utilize PostgreSQL instead of Kener's default SQLite database. (Don't forget to set `DATABASE_URL` in `kener` service to be: `DATABASE_URL=postgresql://db_user:db_password@localhost:5432/kener_db`)
postgres:
image: postgres:alpine
container_name: postgres
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: some_super_random_secure_password # Best to define this in `.env` or via Docker Secret!!
POSTGRES_DB: kener_db
restart: unless-stopped
# Only use below section if you would like to utilize MySQL instead of Kener's default SQLite database. (Don't forget to set `DATABASE_URL` in `kener` service to be: `DATABASE_URL=mysql://db_user:db_password@localhost:3306/kener_db`)
mysql:
image: mariadb:11
container_name: mysql
environment:
MYSQL_USER: user
MYSQL_PASSWORD: some_super_random_secure_password # Best to define this in `.env` or via Docker Secret!!
MYSQL_DATABASE: kener_db
MYSQL_RANDOM_ROOT_PASSWORD: true
restart: unless-stopped
volumes:
data:
name: kener_db
@@ -5,10 +5,5 @@ export NODE_ENV=production
cd /app || exit
# 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
)
exec \
s6-setuidgid abc /usr/bin/node $NODE_ARGS /app/main.js
-1255
View File
File diff suppressed because it is too large Load Diff
+20 -20
View File
@@ -64,24 +64,24 @@ Body of the webhook will be sent as below:
```json
{
"id": "mockoon-9",
"alert_name": "Mockoon DOWN",
"severity": "critical",
"status": "TRIGGERED",
"source": "Kener",
"timestamp": "2024-11-27T04:55:00.369Z",
"description": "🚨 **Service Alert**: Check the details below",
"details": {
"metric": "Mockoon",
"current_value": 1,
"threshold": 1
},
"actions": [
{
"text": "View Monitor",
"url": "https://kener.ing/monitor-mockoon"
}
]
"id": "mockoon-9",
"alert_name": "Mockoon DOWN",
"severity": "critical",
"status": "TRIGGERED",
"source": "Kener",
"timestamp": "2024-11-27T04:55:00.369Z",
"description": "🚨 **Service Alert**: Check the details below",
"details": {
"metric": "Mockoon",
"current_value": 1,
"threshold": 1
},
"actions": [
{
"text": "View Monitor",
"url": "https://kener.ing/monitor-mockoon"
}
]
}
```
@@ -108,7 +108,7 @@ The discord message when alert is `TRIGGERED` will look like this
The discord message when alert is `RESOLVED` will look like this
![Discord](/discord_resolved.png)
![Discord](/documentation/discord_resolved.png)
### Slack
@@ -118,7 +118,7 @@ The slack message when alert is `TRIGGERED` will look like this
The slack message when alert is `RESOLVED` will look like this
![Slack](/slack_resolved.png)
![Slack](/documentation/slack_resolved.png)
### Add Alerts to Monitors
+36
View File
@@ -0,0 +1,36 @@
---
title: View Alerts | Kener
description: Learn how to view alerts in kener.
---
# View Alerts
Alerts are used to notify you when a monitor goes down or up. You can view alerts in the Kener dashboard.
## ID
The unique ID of the alert.
## Type
The type of status that the alert is for. Example DOWN, DEGRADED
## Monitor
The monitor for which the alert is triggered.
## Status
The status of the alert. TRIGGERED, RESOLVED
## Checks
The number of checks that have been done.
## Incident
The incident that the alert is part of.
## Created At
The time when the alert was created.
+16
View File
@@ -0,0 +1,16 @@
---
title: API Keys | Kener
description: Learn how to set up and work with API keys in kener.
---
# API Keys
API keys can be used to call the [APIs of kener](/docs/kener-apis). You can create multiple API keys and use them to call the APIs.
API keys once generated cannot be viewed again so please keep them safe and secure.
## Create API Key
1. To create an API key click on the `Create API Key` button in the API Keys page.
2. Add a name for the key so that it is easy for you to remember
3. Click on Create.
+1 -1
View File
@@ -20,7 +20,7 @@ Let us add a category to our monitors.
- name: Earth
description: Our blue planet
tag: "earth"
defaultStatus: "UP"
default_status: "UP"
image: "/earth.png"
category: "Hello"
- name: Frogment
+85 -35
View File
@@ -5,62 +5,112 @@ description: Changelogs for Kener
# Changelogs
## v2.0.0
Here are the changelogs for Kener. Changelogs are only published when there are new features or breaking changes.
## v3.2.16
### ✨ Features
- **Hidden Categories:** You can now create hidden categories that will not show up for users that are not logged in. All the monitors will also not show up. Some one however can directly get to see them if they visit the monitor link directly
- **Configure data rage:** Now you can configure the data range in home page between 1 and 180 days. You can set different ranges for mobile and desktop devices.
## v3.2.14
### ✨ Features
- **Gamedig Monitor Functionality:** Introduced a new monitor type using Gamedig to monitor over 320+ games and services. Includes options for host, port, timeout, game/service selection, and documentation updates. ([#389](https://github.com/rajnandan1/kener/pull/389) by ToxykAuBleu)
- **Event Subscription:** Added functionality for event subscriptions, involving new database tables (`subscribers`, `subscriptions`, `subscription_triggers`), UI enhancements, a comprehensive game list, and package version update (3.2.13 to 3.2.14). ([#385](https://github.com/rajnandan1/kener/pull/385) by rajnandan1)
- **CRUD API for Monitors:** Implemented API endpoints for creating, reading, updating, and deleting monitors (`GET /api/monitor`, `POST /api/monitor`, `GET /api/monitor/[monitor_id]`, `PUT /api/monitor/[monitor_id]`, `DELETE /api/monitor/[monitor_id]`). Includes OpenAPI spec updates. ([#384](https://github.com/rajnandan1/kener/pull/384) by jensvandenreyt)
- **Modify Monitor Data:** Added the ability for users to modify historical monitoring data via a new UI option and backend API, useful for correcting past statuses (e.g., false positives). ([#380](https://github.com/rajnandan1/kener/pull/380) by mcorbin)
- **Site Status Banner:** Now you can choose to show a site status banner in the main home page. The setting has to be turned on in the Home section of the kener portal.
- **Upcoming Maintenance**: Dedicated Page for upcoming maintenance events.
- **Event Page**: Dedicated page for each event (incident/maintenance)
- **Admin Portal UI Update**: Revamped kener management portal for better accessibility.
### 🐛 Bug Fixes
- **SMTP Secure Variable Fix:** Corrected the evaluation of the `SMTP_SECURE` environment variable to properly handle values like '0' or empty strings, preventing SSL errors with STARTTLS. ([#392](https://github.com/rajnandan1/kener/pull/392) by myned)
- **Group Monitor Fix:** Fix group monitor using queues
### 🌍 Internationalization
- **Polish Translation:** Added Polish language support (`pl.json`) and integrated it into the application's localization framework. ([#386](https://github.com/rajnandan1/kener/pull/386) by lolen)
## v3.2.5
<picture>
<source srcset="https://fonts.gstatic.com/s/e/notoemoji/latest/1f680/512.webp" type="image/webp">
<img src="https://fonts.gstatic.com/s/e/notoemoji/latest/1f680/512.gif" alt="🚀" width="32" height="32">
</picture>
Here are the changes in this release
### Features
- Added support for sqlite3 and removed dependency on file system
- Added support for postgres database. Read more [here](/docs/database)
- Added support for alerting. Read more [here](/docs/alerting)
- Added color customization. Read more [here](/docs/customize-site#color)
- Added three new customizations for home page. Read more [here](/docs/customize-site#barstyle)
- `barStyle`
- `barRoundness`
- `summaryStyle`
- Added code editing for all JavaScript and JSON fields for trigger custom body and monitor `evals`.
- Syntax highlighting and error checking for monitor evaluation functions
- Added support for custom Discord and Slack body templates in triggers
- Improved variable templating for all notification types
- Added comprehensive user management with 3 roles: admin, editor, and member. Read more [here](/docs/rbac)
- Self-service profile management for all users
- User activation/deactivation controls for admins
- Added dedicated Badges management page in the admin dashboard
- Site-wide status badges to show overall service health
- Version information automatically pulled from package.json
### Migration
### Improvements
Kener will automatically migrate your data from file system to sqlite3. If you are using a custom domain, you need to update the `site.yaml` file with the new `siteURL` field. Read more [here](/docs/customize-site#siteURL)
- Improved build time by migrating lucid-svelte to individual components
## v0.0.16
### Github Issues Resolved
- Feature: Enhancement of Webhook Custom Body Functionality [#238](https://github.com/rajnandan1/kener/issues/238)
- Feature: Add LDAP for authentication [#210](https://github.com/rajnandan1/kener/issues/210)
- the event created by tigger can't update affects. [#309](https://github.com/rajnandan1/kener/issues/309)
- Multiple account or RBAC [#334](https://github.com/rajnandan1/kener/issues/334)
- The event that has been edited isn't clickable after you save it [#350](https://github.com/rajnandan1/kener/issues/350)
- /app/package.json ERROR on fresh install [#346](https://github.com/rajnandan1/kener/issues/346)
- Site stops when monitor processing throws an error [#345](https://github.com/rajnandan1/kener/issues/345)
- Support for Self Signed Certificates [#351](https://github.com/rajnandan1/kener/issues/351)
- Wrong month is showing up after clicking on the 'Browse Events' link [#349](https://github.com/rajnandan1/kener/issues/349)
### Migration Notes
This update is fully backward compatible with existing installations. User accounts created before this update will retain admin privileges by default.
## v3.2.1
<picture>
<source srcset="https://fonts.gstatic.com/s/e/notoemoji/latest/1f680/512.webp" type="image/webp">
<img src="https://fonts.gstatic.com/s/e/notoemoji/latest/1f680/512.gif" alt="🚀" width="32" height="32">
</picture>
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 `<title>` and `siteName` is actually the name of the site. Read more [here](/docs/customize-site#siteName)
- **New Heartbeat Monitors**
### Migration
- Added support for push-based monitoring via heartbeats
- Monitors systems that send periodic signals to confirm they're running
- Configurable thresholds for degraded and down states
- Secured with secret token authentication
- Accessible via simple HTTP API endpoints
#### Source
- **Data Interpolation Improvements**
- Fixed data interpolation issues for more accurate uptime calculations
- Improved handling of timestamp boundaries
- Move data from `PUBLIC_KENER_FOLDER` to `/database` file.
- Move `site.yaml` to `/config` folder
- Move `monitors.yaml` to `/config` folder
### Fixes & Improvements
#### Docker
- **UI Enhancements**
- Use `-v $(pwd)/database:/app/database` and `-v $(pwd)/config:/app/config` in your docker run command
- Better mobile responsiveness for theme toggles and controls
- Fixed layout issues with bottom navigation buttons
- Improved timezone selector display
- **Data Processing**
- Fixed edge case issues with uptime calculations
- Improved handling of "No Data" status in summaries
- More accurate time range display across timezones
- **Documentation**
- Added comprehensive documentation for Heartbeat Monitors
- Updated API documentation with new endpoints
+116
View File
@@ -0,0 +1,116 @@
---
title: Concepts | Kener
description: Concepts for Kener
---
# Concepts
Here are the different concepts that Kener uses to make your life easier.
## Monitors
Monitors are the heart of Kener. They are the ones that keep an eye on your services and let you know when something goes wrong. Monitors can be of different types like HTTP, DNS, Ping etc. They can be configured to check your services at different intervals and alert you when something goes wrong.
### HTTP Monitor
HTTP Monitor is used to check the status of a website or any http api. It can be configured to check the status of the website at different intervals.
It can also be configured to check for a specific status code or a specific response.
You can also add secrets to the HTTP request using environment variables.
### DNS Monitor
DNS Monitor is used to check the status of a DNS server. It can be configured to check the status of the DNS server at different intervals.
### Ping Monitor
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
Triggers are the ones that are responsible for sending alerts when something goes wrong. They can be configured to send alerts to different channels like Email, Slack, Discord, Webhook etc.
You can set multiple triggers of the same type.
### Email
Email Trigger is used to send an email when something goes wrong. It can be configured to send an email to different email addresses.
Kener uses [resend.com](https://resend.com) to send emails. Please make sure to sign up in [resend.com](https://resend.com) and get the API key. You will need to set the API key in the environment variable `RESEND_API_KEY`. Learn more about environment variables [here](/docs/environment-vars).
### Slack
Slack Trigger is used to send a message to a slack channel when something goes wrong. It can be configured to send a message to different slack channels. Needs slack URL
### Discord
Discord Trigger is used to send a message to a discord channel when something goes wrong. It can be configured to send a message to different discord channels. Needs discord URL
### Webhook
Webhook Trigger is used to send a message to a webhook when something goes wrong. It can be configured to send a message to different webhooks. Needs webhook URL. Method will be POST.
Body of the webhook will be sent as below:
```json
{
"id": "mockoon-9",
"alert_name": "Mockoon DOWN",
"severity": "critical",
"status": "TRIGGERED",
"source": "Kener",
"timestamp": "2024-11-27T04:55:00.369Z",
"description": "🚨 **Service Alert**: Check the details below",
"details": {
"metric": "Mockoon",
"current_value": 1,
"threshold": 1
},
"actions": [
{
"text": "View Monitor",
"url": "https://kener.ing/monitor-mockoon"
}
]
}
```
| Key | Description |
| --------------------- | ----------------------------------------------------------- |
| id | Unique ID of the alert |
| alert_name | Name of the alert |
| severity | Severity of the alert. Can be `critical`, `warn` |
| status | Status of the alert. Can be `TRIGGERED`, `RESOLVED` |
| source | Source of the alert. Can be `Kener` |
| timestamp | Timestamp of the alert |
| description | Description of the alert. This you can customize. See below |
| details | Details of the alert. |
| details.metric | Name of the monitor |
| details.current_value | Current value of the monitor |
| details.threshold | Alert trigger hreshold of the monitor |
| actions | Actions to be taken. Link to view the monitor. |
---
## Incidents
Incidents are the ones that are created when something goes wrong. They can be created using Manage portal or using APIs.
There can be two types of incidents - `DOWN` and `DEGRADED`. Incident can have a title, description, severity and status. Status can be `INVESTIGATING`, `IDENTIFIED`, `MONITORING` and `RESOLVED`.
To learn more about incidents, click [here](/docs/incident-management).
### Incident Updates
Incident updates are done using comment. Each comment has a status and a timestamp. The status of the latest added comment becomes the current status of the incident.
### Incident Monitors
Monitors can be added as affected components for an incident. Depending on the severity of the incident the status of the monitor also changes.
+27
View File
@@ -0,0 +1,27 @@
---
title: How to Add Custom Fonts | Kener
description: Kener allows you to add custom fonts to your site. This guide will help you add custom fonts to your Kener site.
---
# Add Custom Fonts
1. Login to your Kener dashboard.
2. Go to the `Theme` page.
3. Scroll down to the `Fonts` section.
4. Remove `Font URL` and `Font Name`
5. In Custom CSS add your font
```css
@font-face {
font-family: "CustomFont";
src:
url("/path-to-font/CustomFont.woff2") format("woff2"),
url("/path-to-font/CustomFont.woff") format("woff"),
url("/path-to-font/CustomFont.ttf") format("truetype");
font-weight: normal;
font-style: normal;
}
* {
font-family: "CustomFont", sans-serif;
}
```
+61
View File
@@ -15,6 +15,10 @@ Add your custom JS to `static/` file. And in the `src/app.html` file, add the fo
## Adding Custom CSS
Three are two ways you can add custom CSS to your Kener instance.
### CSS file
Add your custom CSS to `static/` file. And in the `src/app.html` file, add the following line:
```html
@@ -22,3 +26,60 @@ Add your custom CSS to `static/` file. And in the `src/app.html` file, add the f
```
Do not forget to add the base path if you are using a subpath. For example, if you are using a subpath `/kener`, then the path should be `/kener/your-custom-js-file.js`.
### Inline CSS
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 &#x3C;style&#x3E; tags.
</div>
## Grid Layout
To change from a column layout to a grid layout in your Kener instance, you can use the following CSS:
```css
@media (min-width: 1330px) {
.section-monitors,
.section-categories,
.section-hero,
.section-back,
.section-events,
.section-legend,
.section-categories,
.section-browser-events {
width: 1330px !important;
max-width: 1330px !important;
}
.section-monitors .monitor-root {
background-color: transparent;
border: none !important;
box-shadow: none;
}
.section-monitors .monitor-root .monitors-card {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 0.5rem;
}
.section-monitors .monitor-root .monitors-card .monitor {
grid-column: span 1;
border-radius: 0.375rem;
background-color: hsl(var(--card) / var(--tw-bg-opacity, 1));
border-width: 1px;
}
.section-monitors .monitor-root .monitors-card .monitor:last-child {
border-bottom: inherit;
border-bottom-width: 1px !important;
}
}
```
+3 -3
View File
@@ -50,7 +50,7 @@ i18n:
vi: "Tiếng Việt"
pattern: "squares"
font:
cssSrc: "https://fonts.googleapis.com/css2?family=Lato:ital,wght@0,100;0,300;0,400;0,700;0,900;1,100;1,300;1,400;1,700;1,900&display=swap"
cssSrc: "https://fonts.bunny.net/css?family=lato:100,100i,300,300i,400,400i,700,700i,900,900i&display=swap"
family: '"Lato", sans-serif'
analytics:
- id: "G-QsFT"
@@ -315,11 +315,11 @@ You can set the background pattern of the site. It can be `squares`, `dots`, `no
## font
You can set the font of the site. You can use google fonts or any other font.
You can set the font of the site. You can use google fonts, [bunny fonts](https://fonts.bunny.net/) (which is a fully GDPR-compliant, zero-tracking/no-logging CDN font service), or any other font.
```yaml
font:
cssSrc: "https://fonts.googleapis.com/css2?family=Lato:ital,wght@0,100;0,300;0,400;0,700;0,900;1,100;1,300;1,400;1,700;1,900&display=swap"
cssSrc: "https://fonts.bunny.net/css?family=lato:100,100i,300,300i,400,400i,700,700i,900,900i&display=swap"
family: '"Lato", sans-serif'
```
+27 -28
View File
@@ -3,29 +3,28 @@ title: Database Config - Server.yaml - Kener
description: Add database configuration to your kener server.yaml
---
# Database Config
# Databases
Use the `config/server.yaml` file to configure the database settings.
Kener uses relational database to store its data.
## Supported Database
- Sqlite (default)
- Postgres
- Mysql
We are adding more database support in the future.
We will be adding more database support in the future.
## Sqlite
Sqlite is the default database for Kener. You don't need to do anything to use it. The database file will be created in the `database` folder.
Sqlite is the default database for Kener. You don't need to do anything to use it. The database file will be created in the `database` folder which is present in the root of the project.
The name of the default database file is `kener.db`. The path will be `database/kener.db`.
The name of the default database file is `kener.db`. The path will be `database/kener.sqlite.db`.
You can change the database file name by changing the `database` key in the `server.yaml` file.
You can change the database file name by changing the environment variable `DATABASE_URL`. Prefix the connection string with `sqlite://` and add the path to the database file.
```yaml
database:
sqlite:
dbName: awesomeKener.db
```bash
export DATABASE_URL=sqlite://./database/awesomeKener.db
```
In this case, the database file will be created in the `database` folder with the name `awesomeKener.db`.
@@ -34,26 +33,26 @@ Make sure the `database` folder is writable by the Kener process.
## Postgres
To use Postgres, you need to provide the connection details in the `server.yaml` file.
To use Postgres, you need to update the connection string details in the `DATABASE_URL` environment variable. The connection string has to with `postgresql`
```yaml
database:
postgres:
host: localhost
port: 5432
user: kener
password: kener
database: kener
```bash
export DATABASE_URL=postgresql://myuser:mypassword@your.host:5432/kenerdb
```
Or if you want to use environment variables, you can do that as well. Make sure the environment variables are set before starting the Kener process. The environment variables should be `PG_HOST`, `PG_PORT`, `PG_USER`, `PG_PASSWORD`, and `PG_DB`.
## Mysql
```yaml
database:
postgres:
host: $PG_HOST
port: $PG_PORT
user: $PG_USER
password: $PG_PASSWORD
database: $PG_DB
To use Mysql, you need to update the connection string details in the `DATABASE_URL` environment variable. The connection string has to with `mysql`
```bash
export DATABASE_URL=mysql://root:password@your.host:3306/kenerdb
```
## Migrations
To run migrations to latest, run
```bash
npm run migrate
```
When kener runs it will always migrate up to the latest version
+98 -45
View File
@@ -11,29 +11,25 @@ Kener can be deployed in multiple ways. You can use the pre-built docker image o
Make sure you have the following installed:
- [Node.js](https://nodejs.org/en/download/)
- [Node.js 20 or above](https://nodejs.org/en/download/)
- [npm](https://www.npmjs.com/get-npm)
- Make sure `./database` and `./config` directories are present in the root directory
- [config/site.yaml](/docs/customize-site) Contains information about the site
- [config/monitors.yaml](/docs/monitors) Contains your monitors and their related specifications
- [Sqlite3](https://www.sqlite.org/download.html)
- Make sure `./database` directory is present in the root directory
- [Set up Environment Variables](/docs/environment-vars). You can use a `.env` file or pass them as arguments. Be sure to add `NODE_ENV=production` for production deployment
## NPM
```shell
```bash
npm i
npm run build
npm run configure
npm run prod
npm run build #build the frontend
npm run start
```
## PM2
```shell
```bash
npm i
npm run build #build the frontend
npm run configure #build the backend
pm2 start src/lib/server/startup.js
pm2 start main.js
```
@@ -41,40 +37,59 @@ pm2 start main.js
[Dockerhub](https://hub.docker.com/r/rajnandan1/kener)
```shell
```bash
docker.io/rajnandan1/kener:latest
```
[Github Packages](https://github.com/rajnandan1/kener/pkgs/container/kener)
```shell
```bash
ghcr.io/rajnandan1/kener:latest
```
You should mount two host directories to persist your configuration and database. [Environmental variables](/docs/environment-vars) can be passed with `-e` An example `docker run` command:
### Volumes
Make sure `./database` and `./config` directories are present in the root directory
- `$(pwd)/uploads` You should mount one host directories to persist your image uploads.
- `$(pwd)/database` If you are using sqlite, you should mount one host directory to persist your database.
```shell
mkdir database
mkdir config
curl -o config/site.yaml https://raw.githubusercontent.com/rajnandan1/kener/refs/heads/main/config/site.example.yaml
curl -o config/monitors.yaml https://raw.githubusercontent.com/rajnandan1/kener/refs/heads/main/config/monitors.example.yaml
curl -o config/server.yaml https://raw.githubusercontent.com/rajnandan1/kener/refs/heads/main/config/server.example.yaml
### Environment Variables
[Environment variables](/docs/environment-vars) can be passed with `-e` An example `docker run` command:
Make sure `./database` and `./uploads` directories are present in the root directory.
### Examples
This example is for sqlite. You can also use postgres. Read more about it [here](/docs/environment-vars#database-url)
#### sqlite
```bash
mkdir database uploads
```
```bash
docker run \
-v $(pwd)/database:/app/database \
-v $(pwd)/config:/app/config \
-v $(pwd)/uploads:/app/uploads \
-p 3000:3000 \
-e "GH_TOKEN=1234" \
-e "KENER_SECRET_KEY=somesecretkey" \
-e "ORIGIN=http://localhost:3000" \
rajnandan1/kener
```
#### .env
You can also use a .env file
```shell
```bash
mkdir database uploads
```
```bash
docker run \
-v $(pwd)/database:/app/database \
-v $(pwd)/config:/app/config \
-v $(pwd)/uploads:/app/uploads \
--env-file .env \
-p 3000:3000 \
rajnandan1/kener
@@ -82,28 +97,66 @@ docker run \
Or use **Docker Compose** with the example [docker-compose.yaml](https://raw.githubusercontent.com/rajnandan1/kener/main/docker-compose.yml)
## Using PUID and PGID
#### Postgres
If you are
- running on a **linux host** (ie unraid) and
- **not** using [rootless containers with Podman](https://developers.redhat.com/blog/2020/09/25/rootless-containers-with-podman-the-basics#why_podman_)
then you must set the [environmental variables **PUID** and **PGID**.](https://docs.linuxserver.io/general/understanding-puid-and-pgid) in the container in order for it to generate files/folders your normal user can interact it.
Run these commands from your terminal
- `id -u` -- prints UID for **PUID**
- `id -g` -- prints GID for **PGID**
Then add to your docker command like so:
```shell
docker run -d ... -e "PUID=1000" -e "PGID=1000" ... rajnandan1/kener
```bash
mkdir uploads
```
or substitute them in [docker-compose.yml](https://raw.githubusercontent.com/rajnandan1/kener/main/docker-compose.yml)
Database folder is not required for postgres.
## Base path
```bash
docker run \
-p 3000:3000 \
-v $(pwd)/uploads:/app/uploads \
-e "KENER_SECRET_KEY=somesecretkey" \
-e "DATABASE_URL=postgresql://myuser:mypassword@localhost:5432/mydatabase" \
-e "ORIGIN=http://localhost:3000" \
rajnandan1/kener
```
By default kener runs on `/` but you can change it to `/status` or any other path. Read more about it [here](/docs/environment-vars/#kener-base-path)
#### MySQL
```bash
mkdir uploads
```
Database folder is not required for mysql.
```bash
docker run \
-p 3000:3000 \
-v $(pwd)/uploads:/app/uploads \
-e "KENER_SECRET_KEY=somesecretkey" \
-e "DATABASE_URL=mysql://root:password@mysql-container-2.orb.local:3306/kener-2" \
-e "ORIGIN=http://localhost:3000" \
rajnandan1/kener
```
#### Base path
By default kener runs on `/` but you can change it to `/status` or any other path. Read more about it [here](/docs/environment-vars/#kener-base-path).
<div class="note info">
- Important: The base path should _**NOT**_ have a trailing slash and should start with `/`
- Important: This env variable should be present during both build and run time
- Important: You will have to build the frontend with the same base path
</div>
Let us say you are running kener on a subpath `/status`. You can set the base path like this:
```bash
docker build --build-arg KENER_BASE_PATH=/status -t kener .
```
```bash
docker run -p 3000:3000 --env-file .env -v $(pwd)/database:/app/uploads -v $(pwd)/database:/app/database kener
```
## Railway
You can deploy Kener on [Railway](https://railway.app) with a single click.
[![Deploy on Railway](https://railway.com/button.svg)](https://railway.com/template/spSvic?referralCode=1Pn7vs)
+14 -31
View File
@@ -12,35 +12,31 @@ There are two ways to embed your monitor in your website
You can embed your monitor in your website using javascript. We recommend using this method as it takes care of the height of the embedded monitor.
```html
<script
async
src="http://[hostname]/embed-[tag]/js?theme=light&monitor=http://[hostname]/embed-[tag]"
></script>
<script async src="http://[hostname]/embed/monitor-[tag]/js?theme=light&monitor=http://[hostname]/embed/monitor-[tag]"></script>
```
Here is an example
```html
<script
async
src="https://kener.ing/embed-okbookmarks/js?theme=light&monitor=https://kener.ing/embed-okbookmarks"
></script>
<script async src="https://kener.ing/embed/monitor-earth/js?theme=light&monitor=https://kener.ing/embed/monitor-earth"></script>
```
### Parameters
You can pass the following parameters to the embed code
- `theme`: You can pass `light` or `dark` theme
- `monitor`: The monitor url
- `bgc`: Background color of the monitor. Only supports hex color codes. DO NOT include the `#` symbol
- `theme`: You can pass `light` or `dark` theme
- `monitor`: The monitor url
- `bgc`: Background color of the monitor. Only supports hex color codes. DO NOT include the `#` symbol. Example: `ff0000`
- `locale`: The locale of the monitor. You can pass the code of the locale you have enabled in your kener settings. Example: `en`, `fr` etc
- `tz`: Timezone. Example `Asia/Calcutta`, `Asia/Tokyo`
Replace `[hostname]` with your kener hostname and `[tag]` with your monitor tag.
### Demo
<div class="border mx-auto rounded-sm w-585px">
<script async src="/embed-okbookmarks/js?theme=dark&monitor=/embed-okbookmarks"></script>
<script async src="/embed/monitor-earth/js?theme=dark&monitor=/embed/monitor-earth"></script>
</div>
## Iframe
@@ -48,27 +44,13 @@ Replace `[hostname]` with your kener hostname and `[tag]` with your monitor tag.
This is the simplest way to embed your monitor in your website. You can use the following code to embed your monitor in your website.
```html
<iframe
src="http://[hostname]/embed-[tag]?theme=light"
width="100%"
height="200"
allowfullscreen="allowfullscreen"
allowpaymentrequest
frameborder="0"
></iframe>
<iframe src="http://[hostname]/embed/monitor-[tag]?theme=light" width="100%" height="200" allowfullscreen="allowfullscreen" allowpaymentrequest frameborder="0"></iframe>
```
Here is an example
```html
<iframe
src="https://kener.ing/embed-okbookmarks?theme=light"
width="100%"
height="200"
allowfullscreen="allowfullscreen"
allowpaymentrequest
frameborder="0"
></iframe>
<iframe src="https://kener.ing/embed/monitor-earth?theme=light" width="100%" height="100" allowfullscreen="allowfullscreen" allowpaymentrequest frameborder="0"></iframe>
```
Replace `[hostname]` with your kener hostname and `[tag]` with your monitor tag.
@@ -77,11 +59,12 @@ Replace `[hostname]` with your kener hostname and `[tag]` with your monitor tag.
You can pass the following parameters to the embed code
- `theme`: You can pass `light` or `dark` theme
- `bgc`: Background color of the monitor. Only supports hex color codes. DO NOT include the `#` symbol
- `theme`: You can pass `light` or `dark` theme
- `bgc`: Background color of the monitor. Only supports hex color codes. DO NOT include the `#` symbol. Example: `ff0000`
- `locale`: The locale of the monitor. You can pass the code of the locale you have enabled in your kener settings. Example: `en`, `fr` etc
### Demo
<div class="border mx-auto rounded-sm w-585px">
<iframe src="/embed-okbookmarks?theme=dark" width="100%" height="200" allowfullscreen="allowfullscreen" allowpaymentrequest frameborder="0"></iframe>
<iframe src="/embed/monitor-earth?theme=dark" width="100%" height="100" allowfullscreen="allowfullscreen" allowpaymentrequest frameborder="0"></iframe>
</div>
+102 -56
View File
@@ -9,91 +9,137 @@ Kener needs some environment variables to be set to run properly. Here are the l
All of these are optional but are required for specific features.
## KENER_SECRET_KEY [Required]
Please set a strong secret key for Kener to use for encrypting the data. This is required to run Kener.
```bash
export KENER_SECRET_KEY=a-strong-secret-key
```
## ORIGIN [Required]
Set this to the origin of your website(protocl + hostname + port if there). This is required for CORS.
```bash
export ORIGIN=http://localhost:3000
```
If your host is at let us say `https://example.com`, then set the ORIGIN to `https://example.com`. This is required for CORS.
If you are using a reverse proxy like nginx, make sure to set the `ORIGIN` to the domain name of your website.
```bash
export ORIGIN=https://example.com
```
## PORT
Defaults to 3000 if not specified
```shell
```bash
export PORT=4242
```
## GH_TOKEN
A github token to read issues and create labels. This is required for **incident management**
```shell
export GH_TOKEN=your-github-token
```
## API_TOKEN
To talk to **kener apis** you will need to set up a token. It uses Bearer Authorization
```shell
export API_TOKEN=sometoken
```
## API_IP
While using API you can set this variable to accept request from a **specific IP**
```shell
export API_IP=127.0.0.1
```
## API_IP_REGEX
While using API you can set this variable to accept request from a specific IP that matches the regex. Below example shows an **IPv6 regex**
```shell
export API_IP_REGEX=^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$
```
If you set both API_IP and API_IP_REGEX, API_IP will be given preference
## KENER_BASE_PATH
By default kener runs on `/` but you can change it to `/status` or any other path.
- Important: The base path should _**NOT**_ have a trailing slash and should start with `/`
- Important: This env variable should be present during both build and run time
- If you are using docker you will have to do your own build and set this env variable during `docker build`
- Important: The base path should _**NOT**_ have a trailing slash and should start with `/`
- Important: This env variable should be present during both build and run time
```shell
```bash
export KENER_BASE_PATH=/status
```
## RESEND_API_KEY
Kener uses [resend.com](https://resend.com) to send emails. Please make sure to sign up in [resend.com](https://resend.com) and get the API key. You will need to set the API key in the environment variable `RESEND_API_KEY`.
```bash
export RESEND_API_KEY=re_sometoken
```
## RESEND_SENDER_EMAIL
Set the sender email for the emails that are sent by Kener. This is required for sending emails. If you have not added a domain in resend, you can se something like `Some Name <onboarding@resend.dev>`. We recommend adding a domain in resend and using that email domain.
```bash
export RESEND_SENDER_EMAIL=Some Name <email@domain.com>
```
<div class=" note danger ">
Please note that the RESEND_API_KEY is required for sending emails. If you do not set this, Kener will not be able to send emails. RESEND_SENDER_EMAIL is a must if you forget your password. If you do not want to use resend, you can set SMTP variables.
</div>
## DATABASE_URL
Kener uses a database to store its data. By default, Kener uses sqlite. You can change the database by setting the `DATABASE_URL` environment variable. The connection string has to start with `sqlite`, `postgresql`, or `mysql`. Read more about [database configuration](/docs/database).
```bash
export DATABASE_URL=sqlite://./database/awesomeKener.db
```
## TZ
Set the timezone for the server. Set it to UTC.
```bash
export TZ=UTC
```
## SMTP
Kener can also use SMTP to send emails. You can set the SMTP server details in the environment variables.
```bash
export SMTP_HOST=smtp.example.com
export SMTP_PORT=587
export SMTP_USER=username
export SMTP_PASS=password
export SMTP_SECURE=0
export SMTP_FROM_EMAIL=Some Name <user@example.com>
```
<div class=" note info ">
Generally, port 465 expects implicit SSL (SMTP_SECURE: 1), while port 587 and port 25 usually use STARTTLS (SMTP_SECURE: 0).
So, if you are using SMTP on port 465, set SMTP_SECURE: 1; otherwise, for ports 25 or 587, set SMTP_SECURE: 0.
</div>
<div class=" note danger ">
If you set SMTP variables, Kener will use SMTP to send emails. If you set RESEND_API_KEY, Kener will use resend to send emails. If you do both Kener will use SMTP.
</div>
<div class=" note info ">
If your SMTP provider does require username and password, you can set `SMTP_USER` and `SMTP_PASS` to `-`.
</div>
## Using .env
You can also use a `.env` file to set these variables. Create a `.env` file in the root of the project and add the variables like below
```shell
```bash
KENER_SECRET_KEY=please_change_me
PORT=4242
GH_TOKEN=your-github-token
API_TOKEN=sometoken
API_IP=
API_IP_REGEX=
RESEND_API_KEY=re_sometoken
KENER_BASE_PATH=/status
ORIGIN=http://localhost:3000
```
## Secrets
Kener supports secrets in monitors. Let us say you have a monitor that is API based and you want to keep the API key secret. You can use the `secrets` key in the monitor to keep the API key secret.
Kener supports secrets in monitors. Let us say you have a monitor that is API based and you want to keep the API key secret.
```yaml
- name: Example Secret Monitor
description: Monitor to show how to use secrets
tag: "secret"
api:
method: GET
url: https://api.example.com/users
headers:
Authorization: Bearer $CLIENT_SECRET
```
Example: `https://api.example.com/users` with a header `Authorization: Bearer $CLIENT_SECRET`
In the above example, the `CLIENT_SECRET` is a secret that you can set in the monitor. To properly make this work you will have to set up environment variables like below
You should set the `CLIENT_SECRET` in the your environment variables.
```shell
```bash
export CLIENT_SECRET=your-api-key
```
+24 -8
View File
@@ -9,13 +9,7 @@ Kener uses github for incident management. Issues created in github using certai
## Step 1: Create Github Repository
Create a Github Repository. It can be either public or private. After you have created a repository open `site.yaml` and add them like this
```yaml
github:
owner: "username"
repo: "repository"
```
Create a [Github Repository](https://github.com/new). It can be either public or private.
## Step 2: Create Github Token
@@ -41,6 +35,28 @@ You can create either a classic token or personal access token
## Step 3: Set environment
```shell
```bash
export GH_TOKEN=github_pat_11AD3ZA3Y0
```
## Step 4: Add to Kener
Add your repository details to kener.
![Monitors API](/gh_s.png)
### Github API URL
If you are on github enterprise you can set the github api url. For most users, it will be `https://api.github.com`
### Github Repo
The repository name you created in step 1
### Github Username
Your github username
### Incident History
It is in hours. It means if an issue is created before X hours then kener would not honor it. What it means, is that kener would not show it under active incidents nor it will update the uptime. Default is 30\*24 hours = 720 hours.
+74
View File
@@ -0,0 +1,74 @@
---
title: Home Page Setup | Kener
description: Learn how to set up and work with the home page in kener.
---
# Home Page
Home page is the first page that the user sees when they visit your status page. You can customize the home page to show the information that you want to show.
---
## Hero Section
### Title
A big text that will be shown on the hero section.
### Subtitle
A small text that will be shown below the title.
<div class="border rounded-md">
![Hero Section](/documentation/home_1.png)
</div>
---
## Navigation
You can navigation links to other urls. You can add as many as you want.
![Nav Section](/documentation/home_2.png)
### Icon
A small image to be shown along with the link. Icon is optional
### Title
The title of the link.
### URL
The URL to redirect to when the link is clicked.
![Nav Section](/documentation/home_3.png)
---
## Internationalization
Add multiple languages to your status page. You can add a new language and add translations for all the text that is shown on the status page by following the steps [here](/docs/i18n).
- Adding more than one locales will enable a dropdown in the navbar to select the language.
- Selected languages are stored in cookies and will be used when the user visits the site again.
- There is no auto detection of the language. The user has to manually select the language.
### Default Language
The default language of the status page.
---
## Site Categories
Use this to group monitors. Byh default a readonly home category is created. You can add more categories and move monitors to those categories.
---
## Site Footer
Add footer text or html to the status page. You can add any text that you want to show in the footer. Make sure if you are adding html it is safe and valid.
+32 -105
View File
@@ -1,9 +1,9 @@
---
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 | Open Source Status Page
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 Sveltekit NodeJS Status Page System
# Kener - A Feature Rich & Modern Status Page
<p align="center">
<img src="/newbg.png" width="100%" height="auto" class="rounded-lg shadow-lg" alt="kener example illustration">
@@ -36,114 +36,41 @@ description: Kener is an open-source Node.js status page tool, designed to make
</picture>
</div>
<div class="flex gap-2 kener-home-links">
<div class="flex-1 border rounded-md py-4 px-2 text-center">
<a href="https://kener.ing">Live Demo</a>
</div>
<div class="flex-1 border rounded-md py-4 px-2 text-center">
<a href="https://kener.ing/docs/quick-start">Quick Start</a>
</div>
<div class="flex-1 border rounded-md py-4 px-2 text-center">
<a href="https://kener.ing/docs/quick-start">Clone</a>
</div>
<div class="flex-1 border rounded-md py-4 px-2 text-center">
<a href="https://kener.ing/docs/deployment">Deploy</a>
</div>
<div class="flex-1 border rounded-md py-4 px-2 text-center">
<a href="https://kener.ing/docs/kener-apis">APIs</a>
</div>
<a href="https://kener.ing" class="flex-1 border rounded-md py-4 px-2 text-center">
Live Demo
</a>
<a href="https://kener.ing/docs/quick-start" class="flex-1 border rounded-md py-4 px-2 text-center">
Quick Start
</a>
<a href="https://github.com/rajnandan1/kener" class="flex-1 border rounded-md py-4 px-2 text-center">
Clone
</a>
<a href="https://kener.ing/docs/deployment" class="flex-1 border rounded-md py-4 px-2 text-center">
Deploy
</a>
<a href="https://kener.ing/docs/kener-apis" class="flex-1 border rounded-md py-4 px-2 text-center">
APIs
</a>
</div>
## What is Kener?
## Forget Boring Status Pages. Hello Kener!
Kener is status page system built with Sveltekit and NodeJS. It does not try to replace the Datadogs and Atlassian of the world. It tries to help some who wants to come up with a status page that looks nice and minimum overhead, in a modern way.
**Kener** isn't just another status page it's a beautifully crafted, lightning-fast monitoring system that makes your services look good even when they're down. Built with modern tech (**SvelteKit** + **NodeJS**), it delivers enterprise-grade reliability monitoring without the enterprise price tag (it's completely free!).
It comes with all the basic asks for a status page. It is open-source and free to use.
### Why Teams Are Switching to Kener
Kener name is derived from the word "Kene" which means "how is it going" in Assamese, then .ing because it was a cheaply available domain.
- **📈 Up and running in minutes**, not days from Docker to deployment in less time than it takes to make coffee
- **🎭 Dark mode that actually looks good** because your 3 AM incident responses shouldn't burn your retinas
- **🧩 Ridiculously customizable** make it match your brand or go wild with a unique design
- **⚡ Blazing fast performance** server-rendered for speed that both users and search engines love
- **🌐 Works everywhere** auto-adjusts to visitors' time zones and looks great on any device
- **🔄 Real-time data** with gorgeous visualizations that make your uptime metrics pop
- **🛠️ Modern tooling** without the bloat everything you need, nothing you don't
## Features
### Used by Companies That Value Simplicity and Power
### Monitoring and Tracking
From indie developers to growing startups, teams are choosing Kener when they need reliability monitoring that doesn't require a dedicated DevOps team to maintain.
- Advanced application performance monitoring tools
- Real-time network monitor software capabilities
- Polls HTTP endpoint or Push data to monitor using Rest APIs
- Handles Timezones for visitors
- Categorize Monitors into different Sections
- Cron-based scheduling for monitors. Minimum per minute
- Flexible monitor configuration using YAML
- Construct complex API Polls - Chain, Secrets etc
- Supports a Default Status for Monitors
- Supports base path for hosting in k8s
- Pre-built docker image for easy deployment
"_My team set up Kener in less than an hour, and our customers love the transparency it provides. The embedded widgets on our pricing page increased conversions by showing our reliability upfront._" A happy user
### Customization and Branding
- Customizable status page using yaml or code
- Badge generation for status and uptime of Monitors
- Support for custom domains
- Embed Monitor as an iframe or widget
- Light + Dark Theme
- Internationalization support
### Incident Management
- Create Incidents using Github Issues - Rich Text
- Or use APIs to create Incidents
### User Experience and Design
- 100% Accessibility Score
- Easy installation and setup
- User-friendly interface
- Responsive design for various devices
- Auto SEO and Social Media ready
## Technologies used
- [SvelteKit](https://kit.svelte.dev/)
- [shadcn-svelte](https://www.shadcn-svelte.com/)
## Inspired from
Kener draws inspiration from a comprehensive ecosystem of monitoring and status page solutions, reflecting the diverse landscape of network and application performance tools:
Uptime and Status Page Platforms
- Upptime - GitHub-powered uptime monitoring
- Statuspage - Incident communication platform
- Cachet - Open-source status page system
- Upptrends - Global website monitoring
- Hexometer - Website monitoring and performance tracking
Enterprise Monitoring Solutions
- Pingdom - Website monitoring service
- New Relic - Application performance monitoring
- Datadog - Observability and monitoring platform
- AppDynamics - Application performance management
- Dynatrace - AI-powered full stack monitoring
Network and Infrastructure Monitoring
- StatusCake - Uptime and performance monitoring
- UptimeRobot - Free website monitoring
- Better Uptime - Monitoring and incident management
- Site24x7 - Comprehensive monitoring solution
- Nagios - Infrastructure and network monitoring
- Zabbix - Enterprise-class monitoring solution
- Prometheus - Monitoring and alerting toolkit
- Sentry - Error tracking and performance monitoring
Cloud and Hybrid Monitoring Platforms
- CloudWatch - AWS monitoring service
- Azure Monitor - Microsoft's monitoring solution
- Google Cloud Monitoring - Cloud-native monitoring
## Support Me
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)
## Ready to Transform Your Status Page?
+1 -1
View File
@@ -12,7 +12,7 @@ Kener has two parts.
## Folder structure
```shell
```bash
├── src (svelte frontend files)
├── static (things put here can be referenced directly example static/logo.png -> /logo.png)
├── src
+176 -20
View File
@@ -5,37 +5,193 @@ description: Kener supports multiple languages. You can add translations to your
# i18n
You can add translations to your site. By default it is set to `en`. Available translations are present in `/src/lib/locales/` folders in the root directory. You can add more translations by adding a new file in the `/src/lib/locales` folder.
You can add translations to your site. By default it is set to `en`. Available translations are present in `src/lib/locales/` folders in the root directory. You can add more translations by adding a new file in the `src/lib/locales` folder.
## How to enable a translation
Once you have added a new translation file in the `locales` folder, you can enable it by adding the locale code in the `site.yaml` file.
Kener has made it simple to add new translations.
Let us say you have added a `hi.json` file in the `locales` folder. You can enable it by adding the following to the `site.yaml` file.
### Step 1
```yaml
i18n:
defaultLocale: en
locales:
en: English
hi: हिन्दी
Copy and update the translation file in the `locales` folder. The translation file should be a json file with the locale code as the name of the file. For example, `en.json`, `fr.json`, `de.json`.
<label for="exp2" class="accm">
<input type="checkbox" class="absolute opacity-0" id="exp2" />
<p class=" font-medium p-1 accmt rounded-md showaccm">
<span>↓ Show File</span>
<span>↑ Hide File</span>
</p>
<div class="border rounded-md p-1">
```json
{
"%status for %duration": "%status for %duration",
"120 Days": "120 Days",
"14 Days": "14 Days",
"150 Days": "150 Days",
"180 Days": "180 Days",
"30 Days": "30 Days",
"60 Days": "60 Days",
"7 Days": "7 Days",
"90 Days": "90 Days",
"Availability per Component": "Availability per Component",
"All Systems are Degraded": "All Systems are Degraded",
"All Systems are Down": "All Systems are Down",
"All Systems are Operational": "All Systems are Operational",
"All Systems are in Maintenance": "All Systems are in Maintenance",
"Back": "Back",
"Badge Copied": "Badge Copied",
"Badge": "Badge",
"Browse Events": "Browse Events",
"Change Email": "Change Email",
"Code Copied": "Code Copied",
"Confirm Login": "Confirm Login",
"Copy Code": "Copy Code",
"Copy Link": "Copy Link",
"DEGRADED": "DEGRADED",
"DOWN": "DOWN",
"Dark": "Dark",
"Days": "Days",
"Embed this monitor using &#x3C;script&#x3E; or &#x3C;iframe&#x3E; in your app.": "Embed this monitor using <script> or <iframe> in your app.",
"Embed": "Embed",
"Enter the code": "Enter the code",
"Enter your email": "Enter your email",
"Enter your email to log in. If you are not subscribed, you will be prompted to subscribe.": "Enter your email to log in. If you are not subscribed, you will be prompted to subscribe.",
"Get SVG badge for this monitor": "Get SVG badge for this monitor",
"Get a LIVE Status for this monitor": "Get a LIVE Status for this monitor",
"IDENTIFIED": "IDENTIFIED",
"INVESTIGATING": "INVESTIGATING",
"Incident Updates": "Incident Updates",
"LIVE Status": "LIVE Status",
"Lasted for about %lastedFor": "Lasted for about %lastedFor",
"Light": "Light",
"Link Copied": "Link Copied",
"Login": "Login",
"MAINTENANCE": "MAINTENANCE",
"MONITORING": "MONITORING",
"Maintenance Completed": "Maintenance Completed",
"Maintenance in Progress": "Maintenance in Progress",
"Manage Subscription": "Manage Subscription",
"Mode": "Mode",
"No Data": "No Data",
"No Incident in %date": "No Incident in %date",
"No Incidents": "No Incidents",
"No Monitor Found": "No Monitor Found",
"No Updates Yet": "No Updates Yet",
"Ongoing Incidents": "Ongoing Incidents",
"Pinging": "Pinging",
"Please select specific monitors to receive updates from.": "Please select specific monitors to receive updates from.",
"RESOLVED": "RESOLVED",
"Recent Incidents": "Recent Incidents",
"Recent Maintenances": "Recent Maintenances",
"Share this monitor using a link with others": "Share this monitor using a link with others",
"Share": "Share",
"Some Systems are not working as expected": "Some Systems are not working as expected",
"Some Systems Degraded": "Some Systems Degraded",
"Some Systems Down": "Some Systems Down",
"Some Systems in Maintenance": "Some Systems in Maintenance",
"Standard": "Standard",
"Started %startedAt, lasted for %lastedFor": "Started %startedAt, lasted for %lastedFor",
"Started %startedAt, still ongoing": "Started %startedAt, still ongoing",
"Started %startedAt, will last for %lastedFor more": "Started %startedAt, will last for %lastedFor more",
"Starts %startedAt, will last for %lastedFor": "Starts %startedAt, will last for %lastedFor",
"Starts %startedAt": "Starts %startedAt",
"Status OK": "Status OK",
"Status": "Status",
"Subscribe": "Subscribe",
"Subscribe to Updates": "Subscribe to Updates",
"Subscribe to all monitors": "Subscribe to all monitors",
"Switch to your timezone": "Switch to your timezone",
"Theme": "Theme",
"There are no upcoming maintenances": "There are no upcoming maintenances",
"Timezone set to %tz": "Timezone set to %tz",
"Today": "Today",
"UP": "UP",
"Update Subscription": "Update Subscription",
"Upcoming Maintenance": "Upcoming Maintenance",
"Upcoming Maintenances": "Upcoming Maintenances",
"Updates": "Updates",
"Uptime": "Uptime",
"View in detail": "View in detail",
"We have sent a code to your email. Please enter it below to confirm your login": "We have sent a code to your email. Please enter it below to confirm your login",
"You are logged in as %email": "You are logged in as %email"
}
```
> **_defaultLocale:_** The default locale to be used. This will be the language used when a user visits the site for the first time. It is important to note that the default locale json file should be present in the locales folder.
</div>
</label>
### Step 2
Open `src/lib/locales/locales.json` and add the locale code and the name of the language.
```json
[
{
"code": "en",
"name": "English"
},
{
"code": "hi",
"name": "हिन्दी"
},
{
"code": "zh-CN",
"name": "中文"
},
{
"code": "ja",
"name": "日本語"
},
{
"code": "vi",
"name": "Tiếng Việt"
},
{
"code": "fr",
"name": "Français"
}
]
```
### Step 3
Open `src/lib/i18n/client.js` and add the locale code to the `locales` array.
```js
import { ru, enUS, hi, de, zhCN, vi, ja, nl, da, fr, ko } from "date-fns/locale"
```
This should be valid for the date-fns library. You can find the list of locales [here](https://github.com/date-fns/date-fns/blob/9bb51691f201c3ec05ab832acbc5d478f2e5c47a/docs/i18nLocales.md).
Next add a mapping in the `locales` object.
```js
const locales = { ru, en: enUS, hi, dk: de, "zh-CN": zhCN, vi, ja, nl, dk: da, fr, ko }
```
### Step 4
Restart the server and you will see a dropdown in the navbar to select the language.
<div class="note info">
DO NOT forget to raise a PR with the new translation file so that others can use it too.
</div>
## Variables
There are few variables that you you should not change,
- %hours : This will be replaced by the hours
- %minutes : This will be replaced by the minutes
- %minute : This will be replaced by the minute
- %status : This will be replaced by the status
- %status : This will be replaced by the status
- %lastedFor : This will be replaced by the duration the incident lasted
- %startedAt : This will be replaced by the time the incident started
- %date : This will be replaced by the date
- %duration : This will be replaced by the duration
> **locales:\_** A list of locales that you want to enable. The key is the locale code and the value is the name of the language. The locale code should be the same as the json file name in the locales folder. `en` means `en.json` should be present in the locales folder.
## Dates
Adding more than one locales will enable a dropdown in the navbar to select the language.
Selected languages are stored in cookies and will be used when the user visits the site again.
There is no auto detection of the language. The user has to manually select the language.
The localization for dates are still not there. We are working on it and will be available soon.
+62 -20
View File
@@ -5,34 +5,76 @@ description: Kener uses Github to power incident management using labels
# Incident Management
Kener uses Github to power incident management using labels
Kener lets you manage incidents using its dashboard or APIs.
## Labels
## Create Incident
Kener auto creates labels for your monitors using the `tag` parameter
### Incident Title
- `incident`: If an issue is marked as incident it will show up in kener home page
- `incident-down`: If an issue is marked as incident-down and incident kener would make that monitor down
- `incident-degraded`: If an issue is marked as incident-degraded and incident then kener would make the monitor degraded
- `resolved`: Use this tag to mark the incident has RESOLVED
- `identified`: Use this tag to show that the root cause of the incident has been identified
<span class="text-red-500 text-xs font-semibold">
REQUIRED
</span>
## Creating Incident
The title of the incident. This is used to identify the incident in the dashboard.
Kener uses Github issues to create incidents. Here is how you can create an incident
### Incident Summary
### Using Github
<span class="text-red-500 text-xs font-semibold">
REQUIRED
</span>
- Go to your github repo of kener
- Go to issues
- Create an issue. Give it a title
- In the body add [start_datetime:1702651340] and [end_datetime:1702651140] and add some description. Time is UTC
- Add `incident`, `incident-down` and the monitor tag. This will make the monitor down for 4 minutes
A brief summary of the incident. This is used to give a brief overview of the incident.
If you clone the repo it gives you an issue template to create incidents
### Incident Start Date Time
Here is a [sample incident](https://github.com/rajnandan1/kener/issues/15) for your reference.
<span class="text-red-500 text-xs font-semibold">
REQUIRED
</span>
### Using API
The start date and time of the incident.
You can also create incidents using the API. See the API [here](/docs/kener-apis#create-an-incident---api)
<div class="note info">
Each incident once created will be in INVESTIGATING status. You can update the status of the incident by adding updates as discussed below
</div>
---
## Update Incident
Add comments to the incident to update the status of the incident.
### Time Stamp
<span class="text-red-500 text-xs font-semibold">
REQUIRED
</span>
### Message
<span class="text-red-500 text-xs font-semibold">
REQUIRED
</span>
The message to be added to the incident.
### Status
<span class="text-red-500 text-xs font-semibold">
REQUIRED
</span>
The status of the incident. This can be `INVESTIGATING`, `IDENTIFIED`, `MONITORING`, `RESOLVED`.
### Close Incident
To close an incident, you need to add a message and set the status to `RESOLVED`.
---
## Add Monitors
To add monitors to the incident, you need to add the monitor tag to the incident. This will automatically add the monitor to the incident. You will also get the status of the monitor in the incident. For any event type, this can be `DEGRADED` or `DOWN`. For maintenance event type, this can also be `MAINTENANCE`.
## Maintenance
You can also create a maintenance incident. This is similar to an incident but is used to notify users about maintenance activities. Both start and end date and time are required for maintenance incidents.
+10 -408
View File
@@ -5,26 +5,21 @@ description: Kener gives APIs to push data and create incident.
# Kener APIs
Kener also gives APIs to push data and create incident. Before you use kener apis you will have to set an authorization token called `API_TOKEN`. This also has to be set as an environment variable.
Kener also gives APIs to push data and create incident. Before you use kener apis you will have to generate the API keys using the dashboard.
```shell
export API_TOKEN=some-token-set-by-you
```
1. Go to manage kener dashboard
2. Go to api keys
3. Click on Create New API Key
4. Enter a name for the API key
5. Click on Create
Additonally you can set IP whitelisting by setting another environment token called `API_IP` or `API_IP_REGEX`. If you set both `API_IP` and `API_IP_REGEX`, `API_IP` will be given preference. Read more [here](/docs/environment-vars#api_ip)
You can use the generated API key to authenticate the APIs. It will be not be shown again, so make sure to save it.
## Interactive API Reference
## API Reference
<p class="border p-4 rounded-md">
<picture class="inline">
<source srcset="https://fonts.gstatic.com/s/e/notoemoji/latest/1f916/512.webp" type="image/webp">
<img src="https://fonts.gstatic.com/s/e/notoemoji/latest/1f916/512.gif" alt="🤖" width="32" height="32">
</picture>
<a href="/api-reference">
Click here to view the interactive API reference
</a>
The current version is 3.0.0. We maintain just one version
</p>
[API Reference](/api-reference)
You can download the openapi spec
@@ -32,396 +27,3 @@ You can download the openapi spec
- [YAML](https://raw.githubusercontent.com/rajnandan1/kener/main/openapi.yaml)
---
## Update Status - API
![Static Badge](https://img.shields.io/badge/METHOD-POST-blue?style=flat-square)
The update status API can be used to manually update the state of a monitor from a remote server.
### Request Body
| Parameter | Description |
| ------------------ | ------------------------------------------------------------------------------------ |
| status | `Required` Can be only UP/DOWN/DEGRADED |
| latency | `Required` In Seconds. Leave 0 if not required |
| timestampInSeconds | `Optional` Timestamp in UTC seconds. Defaults to now. Should between 90 Days and now |
| tag | `Required` Monitor Tag set in monitors.yaml |
```shell
curl --request POST \
--url http://your-kener.host/api/status \
--header 'Authorization: Bearer some-token-set-by-you' \
--header 'Content-Type: application/json' \
--data '{
"status": "DOWN",
"latency": 1213,
"timestampInSeconds": 1702405860,
"tag": "google-search"
}'
```
### Response
```json
{
"status": 200,
"message": "success at 1702405860"
}
```
This will update the status of the monitor with tag `google-search` to DOWN at UTC 1702405860
---
## Get Status - API
![Static Badge](https://img.shields.io/badge/METHOD-GET-green?style=flat-square)
Use this API to get the status of a monitor.
### Request
Replace `google-search` with your monitor tag in query param
```shell
curl --request GET \
--url 'http://your-kener.host/api/status?tag=google-search' \
--header 'Authorization: Bearer some-token-set-by-you'
```
### Response
```json
{
"status": "UP",
"uptime": "9.0026",
"lastUpdatedAt": 1706447160
}
```
---
## Create an Incident - API
![Static Badge](https://img.shields.io/badge/METHOD-POST-blue?style=flat-square)
Can be use to create an incident from a remote server
### Request Body
| Parameter | Description |
| ------------- | -------------------------------------------------------- |
| startDatetime | `Optional` When did the incident start in UTC second |
| endDatetime | `Optional` When did the incident end in UTC seconds |
| title | `Required` Title of the incident |
| body | `Optional` Body of the incident |
| tags | `Required` Array of String, Monitor Tags of the incident |
| impact | `Optional` Can be only DOWN/DEGRADED |
| isMaintenance | `Optional` Boolean if incident is a maintenance |
| isIdentified | `Optional` Incident identified |
| isResolved | `Optional` Incident resolved |
```shell
curl --request POST \
--url http://your-kener.host/api/incident \
--header 'Authorization: Bearer some-token-set-by-you' \
--header 'Content-Type: application/json' \
--data '{
"startDatetime": 1702405740,
"endDatetime": 1702405920,
"title": "Outage in Mumbai",
"body": "Login cluster is down in mumbai region",
"tags": ["google-search"],
"impact": "DOWN",
"isMaintenance": false,
"isIdentified": true,
"isResolved": false
}'
```
### Response
```json
{
"createdAt": 1703940450,
"closedAt": null,
"title": "Outage in Mumbai",
"tags": ["google-search"],
"incidentNumber": 12,
"startDatetime": 1702405740,
"endDatetime": 1702405920,
"body": "Login cluster is down in mumbai region",
"impact": "DOWN",
"isMaintenance": false,
"isIdentified": true,
"isResolved": false
}
```
---
## Update an Incident - API
![Static Badge](https://img.shields.io/badge/METHOD-PATCH-yellow?style=flat-square)
Can be use to update an incident from a remote server. It will clear values if not passed
### Request Param
- `incidentNumber`: Number of the incident
### Request Body
| Parameter | Description |
| ------------- | -------------------------------------------------------- |
| startDatetime | `Optional` When did the incident start in UTC second |
| endDatetime | `Optional` When did the incident end in UTC seconds |
| title | `Required` Title of the incident |
| body | `Optional` Body of the incident |
| tags | `Required` Array of String, Monitor Tags of the incident |
| impact | `Optional` Can be only DOWN/DEGRADED |
| isMaintenance | `Optional` Boolean if incident is a maintenance |
| isIdentified | `Optional` Incident identified |
| isResolved | `Optional` Incident resolved |
```shell
curl --request PATCH \
--url http://your-kener.host/api/incident/{incidentNumber} \
--header 'Authorization: Bearer some-token-set-by-you' \
--header 'Content-Type: application/json' \
--data '{
"startDatetime": 1702405740,
"endDatetime": 1702405920,
"title": "Outage in Mumbai",
"body": "Login cluster is down in mumbai region",
"tags": ["google-search"],
"impact": "DOWN",
"isMaintenance": false,
"isIdentified": true,
"isResolved": false
}'
```
### Response
```json
{
"createdAt": 1703940450,
"closedAt": null,
"title": "Outage in Mumbai",
"tags": ["google-search"],
"incidentNumber": 12,
"startDatetime": 1702405740,
"endDatetime": 1702405920,
"body": "Login cluster is down in mumbai region",
"impact": "DOWN",
"isMaintenance": false,
"isIdentified": true,
"isResolved": false
}
```
---
## Get an Incident - API
![Static Badge](https://img.shields.io/badge/METHOD-GET-green?style=flat-square)
Use `incidentNumber` to fetch an incident
### Request Body
```shell
curl --request GET \
--url http://your-kener.host/api/incident/{incidentNumber} \
--header 'Authorization: Bearer some-token-set-by-you' \
```
### Response
```json
{
"createdAt": 1703940450,
"closedAt": null,
"title": "Outage in Mumbai",
"tags": ["google-search"],
"incidentNumber": 12,
"startDatetime": 1702405740,
"endDatetime": 1702405920,
"body": "Login cluster is down in mumbai region",
"impact": "DOWN",
"isMaintenance": false,
"isIdentified": true,
"isResolved": false
}
```
---
## Add Comment - API
![Static Badge](https://img.shields.io/badge/METHOD-POST-blue?style=flat-square)
Add comments for incident using `incidentNumber`
### Request
```shell
curl --request POST \
--url http://your-kener.host/api/incident/{incidentNumber}/comment \
--header 'Authorization: Bearer some-token-set-by-you' \
--header 'Content-Type: application/json' \
--data '{
"body": "comment 1"
}'
```
### Response
```json
{
"commentID": 1873376745,
"body": "comment 1",
"createdAt": 1704123938
}
```
---
## Get Comments - API
![Static Badge](https://img.shields.io/badge/METHOD-GET-green?style=flat-square)
Use this API to fetch all the comments for an incident
### Request
```shell
curl --request GET \
--url http://your-kener.host/api/incident/{incidentNumber}/comment \
--header 'Authorization: Bearer some-token-set-by-you' \
```
### Response
```json
[
{
"commentID": 1873372042,
"body": "comment 1",
"createdAt": 1704123116
},
{
"commentID": 1873372169,
"body": "comment 2",
"createdAt": 1704123139
}
]
```
---
## Update Incident Status - API
![Static Badge](https://img.shields.io/badge/METHOD-POST-blue?style=flat-square)
Use this to API to update the status of an ongoing incident.
### Request Body
| Parameter | Description |
| ------------ | ------------------------------------------------------------ |
| isIdentified | `Optional` Boolean, set it when incident has been identified |
| isResolved | `Optional` Boolean, set it when incident has been resolved |
| endDatetime | `Optional` When did the incident end in UTC seconds |
### Request
```shell
curl --request POST \
--url http://your-kener.host/api/incident/{incidentNumber}/status \
--header 'Authorization: Bearer some-token-set-by-you' \
--header 'Content-Type: application/json' \
--data '{
"isIdentified": true,
"isResolved": false
"endDatetime": 1702405920
}'
```
### Response
```json
{
"createdAt": 1703940450,
"closedAt": null,
"title": "Outage in Mumbai",
"tags": ["google-search"],
"incidentNumber": 12,
"startDatetime": 1702405740,
"endDatetime": 1702405920,
"body": "Login cluster is down in mumbai region",
"impact": "DOWN",
"isMaintenance": false,
"isIdentified": true,
"isResolved": false
}
```
---
## Search Incidents - API
![Static Badge](https://img.shields.io/badge/METHOD-POST-blue?style=flat-square)
Use this to API to search incidents.
### Request Body
| Parameter | Description |
| ------------------ | ---------------------------------------------------------------------------------------------- |
| state | `Optional` open or closed. Default is open |
| tags | `Optional` Comma separated monitor tags, example: earth,google-seach |
| page | `Optional` Page number, starts with 1, defaults to 1 |
| per_page | `Optional` Page size, defaults to 10, max is 100 |
| created_after_utc | `Optional` timestamp in UTC seconds when the incident was created after. Example: 1702405920 |
| created_before_utc | `Optional` timestamp in UTC seconds when the incident was created before . Example: 1702405920 |
| title_like | `Optional` search incidents with title |
### Request
Search incidents that are closed and title contains `hello incident`
```shell
curl --request POST \
--url http://your-kener.host/api/incident?state=closed&title_like=Hello%20Incident \
--header 'Authorization: Bearer some-token-set-by-you' \
--header 'Content-Type: application/json' \
--data '{
"isIdentified": true,
"isResolved": false
"endDatetime": 1702405920
}'
```
### Response
```json
[
{
"createdAt": 1703940450,
"closedAt": null,
"title": "Outage in Mumbai - Hello Incident",
"tags": ["google-search"],
"incidentNumber": 12,
"startDatetime": 1702405740,
"endDatetime": 1702405920,
"body": "Login cluster is down in mumbai region",
"impact": "DOWN",
"isMaintenance": false,
"isIdentified": true,
"isResolved": false
}
]
```
+8 -8
View File
@@ -152,7 +152,7 @@ Below example will call https://api.github.com/repos/rajnandan1/kener/issues. If
})
```
## With defaultStatus UP
## With default_status UP
Each minute it will set the status as UP
@@ -160,7 +160,7 @@ Each minute it will set the status as UP
- name: Earth
description: Our Planet
tag: "earth"
defaultStatus: UP
default_status: UP
```
## With Category
@@ -200,10 +200,10 @@ The below monitor will show DEGRADED if 3 or more degraded status in a day and D
- name: Earth
description: Our blue planet
tag: "earth"
defaultStatus: "UP"
dayDegradedMinimumCount: 3
dayDownMinimumCount: 2
includeDegradedInDowntime: true
default_status: "UP"
day_degraded_minimum_count: 3
day_down_minimum_count: 2
include_degraded_in_downtime: true
```
## Monitor with Alerts
@@ -216,8 +216,8 @@ The below example will trigger an alert if the monitor is DOWN for 10 consecutiv
- name: Earth
description: Our blue planet
tag: "earth"
defaultStatus: "UP"
includeDegradedInDowntime: true
default_status: "UP"
include_degraded_in_downtime: true
alerts:
DOWN:
failureThreshold: 10
+257
View File
@@ -0,0 +1,257 @@
---
title: API Monitors | Kener
description: Learn how to set up and work with API monitors in kener.
---
# API Monitors
API monitors are used to monitor APIs. You can use API monitors to monitor the uptime of your Website or APIs and get notified when they are down.
<div class="border rounded-md">
![Monitors API](/documentation/m_api.png)
</div>
## Timeout
<span class="text-red-500 text-xs font-semibold">
REQUIRED
</span>
The timeout is used to define the time in milliseconds after which the monitor should timeout. If the monitor does not respond within the timeout period, the monitor will be marked as down. For example, `5000` will set the timeout to 5 seconds. It is required and has to be a number greater than 0.
## URL
<span class="text-red-500 text-xs font-semibold">
REQUIRED
</span>
The URL is used to define the URL of the API that you want to monitor. It is required and has to be a valid URL.
## Method
<span class="text-red-500 text-xs font-semibold">
REQUIRED
</span>
The method is used to define the HTTP method that should be used to make the request. It is required and has to be one of the following: `GET`, `POST`, `PUT`, `PATCH`, `DELETE`.
## Headers
The headers are used to define the headers that should be sent with the request. It is optional and has to be a valid JSON object.
## Eval
The eval is used to define the JavaScript code that should be used to evaluate the response. It is optional and has be a valid JavaScript code.
This is an anonymous JS function, it should return a **Promise**, that resolves or rejects to `{status, latency}`, by default it looks like this.
> **_NOTE:_** The eval function should always return a json object. The json object can have only status(UP/DOWN/DEGRADED) and latency(number)
> `{status:"DEGRADED", latency: 200}`.
```javascript
(async function (statusCode, responseTime, responseRaw, modules) {
let statusCodeShort = Math.floor(statusCode/100);
let status = 'DOWN'
if(statusCodeShort >=2 && statusCodeShort <= 3) {
status = 'UP',
}
return {
status: 'DOWN',
latency: responseTime,
}
})
```
- `statusCode` **REQUIRED** is a number. It is the HTTP status code
- `responseTime` **REQUIRED**is a number. It is the latency in milliseconds
- `responseRaw` **REQUIRED** is the raw response of the API
- `modules` is an object that has [`cheerio`](https://www.npmjs.com/package/cheerio) that you can use to parse HTML. `const $ = modules.cheerio.load(responseRaw)`
### Example 1
The following example shows how to use the eval function to evaluate the response. The function checks if the status code is 2XX then the status is UP, if the status code is 5XX then the status is DOWN. If the response contains the word `Unknown Error` then the status is DOWN. If the response time is greater than 2000 then the status is DEGRADED.
```javascript
(async function (statusCode, responseTime, responseRaw, modules) {
let status = "DOWN"
//if the status code is 2XX then the status is UP
if (/^[2]\d{2}$/.test(statusCode)) {
status = "UP"
if (responseTime > 2000) {
status = "DEGRADED"
}
}
//if the status code is 5XX then the status is DOWN
if (/^[5]\d{2}$/.test(statusCode)) status = "DOWN"
if (responseRaw.includes("Unknown Error")) {
status = "DOWN"
}
return {
status: status,
latency: responseTime
}
})
```
### Example 2
This next example shows how to call another API withing eval. It is scrapping the second last script tag from the response and checking if the heading is "No recent issues" then the status is UP else it is DOWN.
```js
(async function (statusCode, responseTime, responseRaw, modules) {
let htmlString = responseRaw;
const scriptTags = htmlString.match(/<script[^>]*src="([^"]+)"[^>]*>/g)
if (scriptTags && scriptTags.length >= 2) {
// Extract the second last script tag's src attribute
const secondLastScript = scriptTags[scriptTags.length - 2]
const srcMatch = secondLastScript.match(/src="([^"]+)"/)
const secondLastScriptSrc = srcMatch ? srcMatch[1] : null
let jsResp = await fetch(secondLastScriptSrc) //api call
let jsRespText = await jsResp.text()
//check if heading":"No recent issues" exists
let noRecentIssues = jsRespText.indexOf('heading":"No recent issues"')
if (noRecentIssues != -1) {
return {
status: "UP",
latency: responseTime
}
}
}
return {
status: "DOWN",
latency: responseTime
}
})
```
### Example 3
The next example shows how to use cheerio to parse bitbucket status page and check if all the components are operational. If all the components are operational then the status is UP else it is DOWN.
```js
(async function (statusCode, responseTime, responseDataBase64, modules) {
let html = atob(responseDataBase64)
const $ = modules.cheerio.load(html)
const components = $(".components-section .components-container .component-container")
let status = true
components.each((index, element) => {
const name = $(element).find(".component-name").text().trim()
const statusText = $(element).find(".component-status").text().trim()
if (statusText !== "Operational") {
status = false
}
})
return {
status: status ? "UP" : "DOWN",
latency: responseTime
}
})
```
## Examples
### Website Monitor
This is an example to monitor google every 5 minute.
<label for="websiteMonitor" class="accm">
<input type="checkbox" class="absolute opacity-0" id="websiteMonitor" />
<p class="font-medium p-1 accmt rounded-md showaccm">
<span>↓ Show Example</span>
<span>↑ Hide Example</span>
</p>
<div class="border rounded-md">
![Monitors API](/documentation/m_ex_website.png)
</div>
</label>
### Get Request
Example to monitor a GET Request with an Authorization header set to `$SOME_TOKEN`.
`$SOME_TOKEN` is set in environment variables as `SOME_TOKEN`
```bash
export SOME_TOKEN=some-token-example
```
<label for="exp2" class="accm">
<input type="checkbox" class="absolute opacity-0" id="exp2" />
<p class=" font-medium p-1 accmt rounded-md showaccm">
<span>↓ Show Example</span>
<span>↑ Hide Example</span>
</p>
<div class="border rounded-md p-1">
![Monitors API](/documentation/m_ex_2.png)
</div>
</label>
### POST Request
Example showing setting up a POST request every minute with a timeout of 2 seconds.
<label for="exp3" class="accm">
<input type="checkbox" class="absolute opacity-0" id="exp3" />
<p class=" font-medium p-1 accmt rounded-md showaccm">
<span>↓ Show Example</span>
<span>↑ Hide Example</span>
</p>
<div class="border rounded-md p-1">
![Monitors API](/documentation/m_ex_3.png)
</div>
</label>
### Custom Response Eval
Example showing how to use a custom response eval function. It also shows how to set secrets in body for POST Request.
The secrets have to be present in environment variables.
```bash
export SERVICE_TOKEN=secret1_tone
export SERVICE_SECRET=secret2_secret
```
<label for="exp4" class="accm">
<input type="checkbox" class="absolute opacity-0" id="exp4" />
<p class=" font-medium p-1 accmt rounded-md showaccm">
<span>↓ Show Example</span>
<span>↑ Hide Example</span>
</p>
<div class="border rounded-md p-1">
![Monitors API](/documentation/m_ex_4.png)
</div>
</label>
+59
View File
@@ -0,0 +1,59 @@
---
title: DNS Monitors | Kener
description: Learn how to set up and work with DNS monitors in kener.
---
# DNS Monitors
DNS monitors are used to monitor DNS servers. Verify DNS queries for your server and match values with the expected values to get notified when they are different.
<div class="border rounded-md">
![Monitors Ping](/documentation/m_dns.png)
</div>
## Host
<span class="text-red-500 text-xs font-semibold">
REQUIRED
</span>
The host is used to define the host of the DNS server that you want to monitor. It is required and has to be a valid host.
## Lookup Record
<span class="text-red-500 text-xs font-semibold">
REQUIRED
</span>
The query is used to define the query that you want to monitor. It is required and has to be a valid query. Example: `A`, `MX`, `TXT`, `CNAME`.
## Name Server
<span class="text-red-500 text-xs font-semibold">
REQUIRED
</span>
The name server is the DNS server that you want to query. It is required and has to be a valid DNS server.
Few examples:
- 8.8.8.8 (Google)
- 1.1.1.1 (Cloudflare)
## Match Type
<span class="text-red-500 text-xs font-semibold">
REQUIRED
</span>
Match type for DNS response. Can be ANY or ALL. If ANY is selected, the monitor will be marked as UP if any of the expected values match the response. If ALL is selected, all the expected values should match the response.
## Expected Values
<span class="text-red-500 text-xs font-semibold">
REQUIRED
</span>
The expected values are the values that you expect the DNS server to return. You can add multiple values. Atleast one is required.
+68
View File
@@ -0,0 +1,68 @@
---
title: Gamedig Monitors | Kener
description: Monitor games and services
---
# Gamedig Monitors
Gamedig monitors are used to monitor games and services. Currently, 320+ games and some services are available. You can view the entire list [here](https://github.com/gamedig/node-gamedig/blob/HEAD/GAMES_LIST.md). More than just the state of the game/service, it provides information about your game/service, such as players list, world name, connection method, and so on.
<div class="border rounded-md">
![Monitors Gamedig](/documentation/m_gamedig.png)
</div>
## Host
<span class="text-red-500 text-xs font-semibold">
REQUIRED
</span>
The host is the domain name, IPv4 or IPv6 of the game/service you want to monitor. The host should be valid.
## Port
<span class="text-red-500 text-xs font-semibold">
REQUIRED
</span>
The port is the query port of the game/service you want to monitor. Some games use a port, different from the one to connect, to accept queries, such as Minecraft with `query-port`.
If you do not know the query port, you can let the monitor guess the port by checking the option `Guess port`.
## Timeout
<span class="text-red-500 text-xs font-semibold">
REQUIRED
</span>
The timeout is used to define the time in milliseconds after which the monitor should timeout. If the monitor does not respond within the timeout period, the monitor will be marked as down. For example, 10000 will set the timeout to 10 seconds. It is required and has to be a number greater than 2000 (or 2 seconds).
## Request additional rules
Valve games can provide additional 'rules' to Gamedig monitors. If checked, they will be available in `reponseRaw.raw` (see [Eval](#eval)), beware that it may increase query time.
## Eval
The eval is used to define the JavaScript code that should be used to evaluate the response. It is optional and has be a valid JavaScript code.
This is an anonymous JS function, it should return a **Promise**, that resolves or rejects to `{status, latency}`, by default it looks like this.
> **_NOTE:_** The eval function should always return a json object. The json object can have only status(UP/DOWN/DEGRADED) and latency(number)
> `{status: "DEGRADED", latency: 200}`.
```javascript
(async function (responseTime, responseRaw) {
return {
status: "UP",
latency: responseTime
}
})
```
- `responseTime` **REQUIRED** is a number. It is the latency in milliseconds
- `responseRaw` **REQUIRED** is the raw response of the game/service. It varies from game by game, we recommend first printing responseRaw with a simple `console.log`.
## Additional notes
Some games/services need more configuration on their part to accept correctly any query of the monitor. You can consult these notes [here](https://github.com/gamedig/node-gamedig/blob/HEAD/GAMES_LIST.md#games-with-additional-notes).
+40
View File
@@ -0,0 +1,40 @@
---
title: Group Monitors | Kener
description: Learn how to set up and work with Group monitors in kener.
---
# Group Monitors
Group monitors are used to monitor multiple monitors at once. You can use Group monitors to monitor multiple monitors at once and get notified when they are down.
<div class="border rounded-md">
![Monitors Group](/documentation/m_group.png)
</div>
## Timeout
<span class="text-red-500 text-xs font-semibold">
REQUIRED
</span>
The timeout is used to define the time in milliseconds after which the group monitor should timeout.
Let us say the group monitor runs every minute, it will expect in the same minute all the other monitors to finish. It will wait till the timeout for them to complete. If not completed within that timeout, it will be marked as down.
## Monitors
<span class="text-red-500 text-xs font-semibold">
REQUIRED
</span>
You can add as many monitors as you want to monitor. The minimum number of monitors required is 2. The monitor can be any type of monitor.
## Hide
You can hide the monitors that are part of the group monitor. If you hide the monitors, the monitors inside the group will not be shown in the home page.
<div class="note">
The group status will be the worst status of the monitors in the group.
</div>
+112
View File
@@ -0,0 +1,112 @@
---
title: Heartbeat Monitors | Kener
description: Heartbeat monitors allow you to monitor systems using a "push" based approach
---
# Heartbeat Monitors
Heartbeat monitors allow you to monitor systems using a "push" based approach, where your services send signals (or "heartbeats") to Kener on a regular basis. If Kener doesn't receive a heartbeat within the configured time period, it will mark the system as degraded or down.
This is useful for monitoring services that:
- Run on a schedule
- Cannot be directly accessed for pull-based monitoring
- Need to report their own health status
## How It Works
1. You configure a heartbeat monitor in Kener
2. Kener provides a unique URL endpoint for your service to ping
3. Your service sends HTTP requests to this endpoint at regular intervals
4. If Kener doesn't receive a ping within the configured time window, it will mark the service as degraded or down
## Setting Up a Heartbeat Monitor
To create a heartbeat monitor:
1. Navigate to the Monitors page in the dashboard
2. Click "Add Monitor"
3. Select "Heartbeat" as the monitor type
4. Configure the following settings:
<div class="border rounded-md">
![Heartbeat Monitor Configuration](/documentation/m_hb.png)
</div>
### Configuration Options
| Option | Description |
| ------------------------ | -------------------------------------------------------------------------------------------- |
| Secret String | A unique secret that authenticates your heartbeat requests |
| Degraded After (Minutes) | Number of minutes after which the service is considered degraded if no heartbeat is received |
| Down After (Minutes) | Number of minutes after which the service is considered down if no heartbeat is received |
## Sending Heartbeats
Your service needs to send HTTP requests to the Kener heartbeat URL. Both GET and POST requests are supported.
### Endpoint Format
```
https://<your-kener-instance>/api/heartbeat/<tag>:<secretString>
```
Where:
- `<tag>` is the tag you configured for your monitor
- `<secretString>` is the secret you configured during setup
### Example
If your monitor has:
- Tag: `nightly-backup`
- Secret String: `super-secret-key`
Your service should send requests to:
```
https://<your-kener-instance>/api/heartbeat/nightly-backup:super-secret-key
```
You can use any HTTP client to send these requests, such as curl:
```bash
curl https://<your-kener-instance>/api/heartbeat/nightly-backup:super-secret-key
```
Or in a scheduled script:
```bash
#!/bin/bash
# Run your task here
./perform-backup.sh
# Send heartbeat to Kener
curl https://<your-kener-instance>/api/heartbeat/nightly-backup:super-secret-key
```
## Monitoring State
The monitor status works as follows:
- **UP**: Heartbeats are being received within the configured time windows
- **DEGRADED**: No heartbeat has been received for longer than the "Degraded After" time period
- **DOWN**: No heartbeat has been received for longer than the "Down After" time period
## Security Considerations
- Keep your secret string confidential, as anyone with knowledge of it can send heartbeats
- Consider using HTTPS for your Kener instance to encrypt heartbeat communications
- Rotate the secret string periodically by updating the monitor configuration
## Best Practices
- Set the "Degraded After" time slightly longer than your expected interval between heartbeats
- Set the "Down After" time based on your SLA requirements
- For critical systems, consider sending heartbeats more frequently than required
- Add logging in your service to track successful and failed heartbeat attempts
Heartbeat monitors provide a simple but effective way to ensure your scheduled tasks and services are running properly, without requiring external polling.
+131
View File
@@ -0,0 +1,131 @@
---
title: Ping Monitors | Kener
description: Learn how to set up and work with Ping monitors in kener.
---
# Ping Monitors
Ping monitors are used to monitor livenees of your servers. You can use Ping monitors to monitor the uptime of your servers and get notified when they are down.
<div class="border rounded-md">
![Monitors Ping](/documentation/m_ping.png)
</div>
## Hosts
You can add as many hosts as you want to monitor. The host can be an IP(IP4 and IP6) address or a domain name.
- Type: Choose the type of host you want to monitor. It can be either `IP4` or `IP6` or `DOMAIN`.
- Host: Enter the IP address or domain name of the host you want to monitor.
- Timeout: Enter the timeout in milliseconds for each ping request of each host. For IP6 timeout is not supported.
- Count: The number of pings you want to do for each host. The average latency of all the pings will be used to evaluate the response.
## Eval
The eval is used to define the JavaScript code that should be used to evaluate the response. It is optional and has be a valid JavaScript code.
This is an anonymous JS function, it should return a **Promise**, that resolves or rejects to `{status, latency}`, by default it looks like this.
> **_NOTE:_** The eval function should always return a json object. The json object can have only status(UP/DOWN/DEGRADED) and latency(number)
> `{status:"DEGRADED", latency: 200}`.
```javascript
(async function (arrayOfPings) {
let latencyTotal = arrayOfPings.reduce((acc, ping) => {
return acc + ping.latency
}, 0)
let alive = arrayOfPings.reduce((acc, ping) => {
return acc && ping.alive
}, true)
return {
status: alive ? "UP" : "DOWN",
latency: latencyTotal / arrayOfPings.length
}
})
```
- `arrayOfPings` **REQUIRED** is an array of Ping Response Objects as shown below.
```json
/*
[
{
alive: true,
min: '145.121',
max: '149.740',
avg: '147.430',
latencies: [ 145.121, 149.74 ],
latency: 145.121,
host: '45.91.169.49',
type: 'IP4'
},
{
alive: true,
min: '145.348',
max: '149.143',
avg: '147.245',
latencies: [ 145.348, 149.143 ],
latency: 145.348,
host: 'kener.ing',
type: 'DOMAIN'
},
{
alive: true,
min: '57.696',
max: '58.330',
avg: '58.013',
latencies: [ 57.696, 58.33 ],
latency: 57.696,
host: '2404:6800:4003:c1c::66',
type: 'IP6'
}
]
*/
```
### Understanding the Input
Each object in the array represents the ping response of a host.
- `alive` is a boolean. It is true if the host is alive and false if the host is down.
- `min` is a string. It is the minimum latency of the pings.
- `max` is a string. It is the maximum latency of the pings.
- `avg` is a string. It is the average latency of the pings.
- `latencies` is an array of numbers. It is the latency of each ping.
- `latency` is a number. It is the average latency of the pings.
- `host` is a string. It is the host that was pinged.
- `type` is a string. It is the type of the host. It can be either `IP4` or `IP6` or `DOMAIN`.
### Example
The following example shows how to use the eval function to evaluate the response. The function checks if the combined latency is more 10ms then returns `DEGRADED`.
```javascript
(async function (arrayOfPings) {
let latencyTotal = arrayOfPings.reduce((acc, ping) => {
return acc + ping.latency
}, 0)
let areAllOpen = arrayOfPings.reduce((acc, ping) => {
return acc && ping.alive
}, true)
let avgLatency = latencyTotal / arrayOfPings.length
if (areAllOpen && avgLatency > 10) {
return {
status: "DEGRADED",
latency: avgLatency
}
}
return {
status: areAllOpen ? "UP" : "DOWN",
latency: avgLatency
}
})
```
+63
View File
@@ -0,0 +1,63 @@
---
title: SQL Monitors | Kener
description: Monitor a SQL database
---
# SQL Monitors
SQL monitors are used to monitor a SQL database. You can monitor a SQL database by adding the database connection details to the monitor. The SQL monitor will check the database connection and notify you if the database connection is down.
<div class="border rounded-md">
![Monitors SQL](/documentation/m_sql.png)
</div>
## Database Type
<span class="text-red-500 text-xs font-semibold">
REQUIRED
</span>
The database type is the type of the database you want to monitor. The database type should be one of the following values:
- mysql
- postgres
Instead of giving the connection string as plain text you can add environment variables to the connection string. The environment variables should be in the format of `$`. The environment variables should be defined in the `.env` file.
Example: `mysql://$MYSQL_USER:$MYSQL_PASSWORD@$MYSQL_HOST:$MYSQL_PORT/$MYSQL_DATABASE`
Make sure to define the environment variables in the `.env` file as shown below
```env
MYSQL_USER=root
MYSQL_PASSWORD=password
MYSQL_HOST=localhost
MYSQL_PORT=3306
MYSQL_DATABASE=database
```
## Timeout
<span class="text-red-500 text-xs font-semibold">
REQUIRED
</span>
The timeout is the time in milliseconds to wait for the database connection to respond.
## Connection String
<span class="text-red-500 text-xs font-semibold">
REQUIRED
</span>
The connection string is the connection string of the database you want to monitor. The connection string should be a valid connection string for the database type you have selected.
## SQL Query
<span class="text-red-500 text-xs font-semibold">
REQUIRED
</span>
The SQL query is the query you want to run on the database. The SQL query should be a valid SQL query for the database type you have selected.
+52
View File
@@ -0,0 +1,52 @@
---
title: SSL Monitors | Kener
description: Monitor the SSL certificate of a domain
---
# SSL Monitors
SSL monitors are used to monitor the SSL certificate of a domain. You can monitor the SSL certificate of a domain by adding the domain name to the monitor. The SSL monitor will check the SSL certificate of the domain and notify you if the SSL certificate is about to expire.
<div class="border rounded-md">
![Monitors SSL](/documentation/m_ssl.png)
</div>
## Host
<span class="text-red-500 text-xs font-semibold">
REQUIRED
</span>
The host is the domain name of the website you want to monitor. The host should be a valid domain name.
## Port
<span class="text-red-500 text-xs font-semibold">
REQUIRED
</span>
The port is the ssl port of the website you want to monitor. The default ssl port is 443.
## Degraded If
<span class="text-red-500 text-xs font-semibold">
REQUIRED
</span>
Set this to the number of hours that still remains for the certificate to expire. If the certificate is about to expire in the given number of hours, the monitor status will be set to degraded. Example if you send the value to be 24, the monitor status will be set to degraded if the certificate is about to expire in 24 hours.
## Down If
<span class="text-red-500 text-xs font-semibold">
REQUIRED
</span>
Set this to the number of hours that still remains for the certificate to expire. If the certificate is about to expire in the given number of hours, the monitor status will be set to down. Example if you send the value to be 12, the monitor status will be set to down if the certificate is about to expire in 12 hours.
<div class="note">
`Degraded If` should be more than `Down If`.
</div>
+130
View File
@@ -0,0 +1,130 @@
---
title: TCP Monitors | Kener
description: Learn how to set up and work with TCP monitors in kener.
---
# TCP Monitors
TCP monitors are used to monitor the liveness of your servers. You can use TCP monitors to monitor the uptime of your servers and get notified when they are down.
<div class="border rounded-md">
![Monitors TCP](/documentation/m_tcp.png)
</div>
## Hosts
You can add as many hosts as you want to monitor. The host can be an IP(IP4 and IP6) address or a domain name.
- Type: Choose the type of host you want to monitor. It can be either `IP4` or `IP6` or `DOMAIN`.
- Host: Enter the IP address or domain name of the host you want to monitor.
- Port: Enter the port number of the host you want to monitor.
- Timeout: Enter the timeout in milliseconds for each ping request of each host
## Eval
The eval is used to define the JavaScript code that should be used to evaluate the response. It is optional and has be a valid JavaScript code.
This is an anonymous JS function, it should return a **Promise**, that resolves or rejects to `{status, latency}`, by default it looks like this.
> **_NOTE:_** The eval function should always return a json object. The json object can have only status(UP/DOWN/DEGRADED) and latency(number)
> `{status:"DEGRADED", latency: 200}`.
```javascript
(async function (arrayOfPings) {
let latencyTotal = arrayOfPings.reduce((acc, ping) => {
return acc + ping.latency
}, 0)
let alive = arrayOfPings.reduce((acc, ping) => {
if (ping.status === "open") {
return acc && true
} else {
return false
}
}, true)
return {
status: alive ? "UP" : "DOWN",
latency: latencyTotal / arrayOfPings.length
}
})
```
- `arrayOfPings` **REQUIRED** is an array of TCP Response Objects as shown below.
```json
/*
[
{
status: 'open',
latency: 31.93,
host: '66.51.120.219',
port: 465,
type: 'IP4'
},
{
status: 'open',
latency: 47.041417,
host: '2404:6800:4003:c1c::66',
port: 80,
type: 'IP6'
},
{
status: 'open',
latency: 82.1865,
host: 'rajnandan.com',
port: 80,
type: 'DOMAIN'
}
]
*/
```
### Understanding the Input
Each object in the array represents the tcp response of a host.
- `host`: The host that was pinged.
- `port`: The port that was pinged. Defaults to 80 if not provided.
- `type`: The type of IP address. Can be `IP4` or `IP6`.
- `status`: The status of the ping. Can be `open` , `error` or `timeout`.
- `open`: The host is reachable.
- `error`: There was an error while pinging the host.
- `timeout`: The host did not respond in time.
- `latency`: The time taken to ping the host. This is in milliseconds.
### Example
The following example shows how to use the eval function to evaluate the response. The function checks if the combined latency is more 10ms then returns `DEGRADED`.
```javascript
(async function (arrayOfPings) {
let latencyTotal = arrayOfPings.reduce((acc, ping) => {
return acc + ping.latency
}, 0)
let areAllOpen = arrayOfPings.reduce((acc, ping) => {
if (ping.status === "open") {
return acc && true
} else {
return false
}
}, true)
let avgLatency = latencyTotal / arrayOfPings.length
if (areAllOpen && avgLatency > 10) {
return {
status: "DEGRADED",
latency: avgLatency
}
}
return {
status: areAllOpen ? "UP" : "DOWN",
latency: avgLatency
}
})
```
+116 -142
View File
@@ -1,181 +1,155 @@
---
title: Monitors | monitors.yaml | Kener
title: Monitors | Kener
description: Monitors are the heart of Kener. This is where you define the monitors you want to show on your site.
---
# Monitors
Inside `config/` folder there is a file called `monitors.yaml`. We will be adding our monitors here. Please note that your yaml must be valid. It is an array.
Monitors are the heart of Kener. This is where you define the monitors you want to show on your site.
## Understanding monitors
## Add Monitors
Each monitor runs at 1 minute interval by default. Monitor runs in below priority order.
Click on the to add a monitor.
- `defaultStatus` Data. Used to set the default status of the monitor
- PING/API/DNS call Data overrides above data(if present)
- Pushed Status Data overrides status Data using [Kener Update Statue API](/docs/kener-apis#update-status---api)
- [Manual Incident](/docs/incident-management) Data overrides Pushed Status Data
<div class="border rounded-md">
## General Attributes
![Monitors Main](/documentation/m_main.png)
A list of attributes that can be used in all types of monitors.
</div>
| Key | Required? | Explanation |
| ------------------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| name | Required + Unique | This will be shown in the UI to your users. Keep it short and unique |
| description | Optional | This is a breif description for the monitor |
| tag | Required + Unique | This is used to tag incidents created in Github using comments |
| image | Optional | To show a logo before the name |
| cron | Optional | Use a valid cron expression to specify the interval to run the monitors. Defaults to `* * * * *` i.e every minute |
| defaultStatus | Optional | This will be the default status if no other way is specified to check the monitor. can be `UP`/`DOWN`/`DEGRADED` |
| hidden | Optional | If set to `true` will not show the monitor in the UI |
| category | Optional | Use this to group your monitors. Make sure you have defined category in `site.yaml` and use the `name` attribute. More about it [here](/docs/customize-site#categories). |
| dayDegradedMinimumCount | Optional | Default is 1. It means, minimum this number of count for the day to be classified as DEGRADED(Yellow Bar) in 90 day view. Has to be `number` greater than 0 |
| dayDownMinimumCount | Optional | Default is 1. It means, minimum this number of count for the day to be classified as DOWN(Red Bar) in 90 day view. Has to be `number` greater than 0 |
| includeDegradedInDowntime | Optional | By deafault uptime percentage is calculated as (UP+DEGRADED/UP+DEGRADED+DOWN). Setting it as `true` will change the calculation to (UP/UP+DEGRADED+DOWN) |
## Tag
### Example
<span class="text-red-500 text-xs font-semibold">
UNIQUE
</span>
&
<span class="text-red-500 text-xs font-semibold">
REQUIRED
</span>
```yaml
- name: "Google"
description: "Google Search Engine"
tag: "google"
image: "https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png"
defaultStatus: "UP"
hidden: false
dayDegradedMinimumCount: 2
dayDownMinimumCount: 3
includeDegradedInDowntime: false
```
The tag is unique to each monitor and is used to identify the monitor in the code. It is also used to generate the URL for the monitor.
`dayDegradedMinimumCount` and `dayDownMinimumCount` only works when `summaryStyle` is set as `DAY` in `site.yaml`. More about it [here](/docs/customize-site#summarystyle)
## Name
## API Monitor Attributes
<span class="text-red-500 text-xs font-semibold">
UNIQUE
</span>
&
<span class="text-red-500 text-xs font-semibold">
REQUIRED
</span>
A list of attributes that can be used in API monitors.
Name is also unique to each monitor and is used to identify the monitor in the admin panel or in the status page.
| Key | Required? | Explanation |
| ----------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| api.url | Required | HTTP URL |
| api.method | Optional | HTTP Method. Default is `GET` |
| api.headers | Optional | HTTP headers |
| api.body | Optional | HTTP Body as string |
| api.timeout | Optional | timeout for the api in milliseconds. Default is 10000(10 secs) |
| api.eval | Optional | Evaluator written in JS, to parse HTTP response and calculate uptime and latency |
| api.hideURLForGet | Optional | if the monitor is a GET URL and no headers are specified and the response body content-type is a text/html then kener shows a GET hyperlink in monitor description. To hide that set this as false. Default is `true` |
## Image
### Eval
The image is used to display a logo or an image for the monitor. It is displayed on the status page and in the admin panel. It is optional.
This is a anonymous JS function, by default it looks like this.
## Description
> **_NOTE:_** The eval function should always return a json object. The json object can have only status(UP/DOWN/DEGRADED) and lantecy(number)
> `{status:"DEGRADED", latency: 200}`.
The description is used to describe the monitor. It is displayed on the status page and in the admin panel. It is optional. You can also write html in the description and can use tailwind classes.
```javascript
(function (statusCode, responseTime, responseDataBase64) {
let statusCodeShort = Math.floor(statusCode/100);
let status = 'DOWN'
if(statusCodeShort >=2 && statusCodeShort <= 3) {
status = 'UP',
}
return {
status: 'DOWN',
latency: responseTime,
}
})
```
## Cron
- `statusCode` **REQUIRED** is a number. It is the HTTP status code
- `responseTime` **REQUIRED**is a number. It is the latency in milliseconds
- `responseDataBase64` **REQUIRED** is a string. It is the base64 encoded response data. To use it you will have to decode it
<span class="text-red-500 text-xs font-semibold">
REQUIRED
</span>
```js
let decodedResp = atob(responseDataBase64);
//let jsonResp = JSON.parse(decodedResp)
```
The cron is used to define the interval at which the monitor should run. It is a cron expression. For example, `* * * * *` will run the monitor every minute.
### Example
## Default Status
```yaml
- name: "Google"
description: "Google Search Engine"
tag: "google"
image: "https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png"
defaultStatus: "UP"
hidden: false
api:
url: "https://www.google.com"
method: "GET"
headers:
"Content-Type": "application/json"
body: ""
timeout: 10000
eval: |
(function (statusCode, responseTime, responseDataBase64) {
let statusCodeShort = Math.floor(statusCode/100);
let status = 'DOWN'
if(statusCodeShort >=2 && statusCodeShort <= 3) {
status = 'UP',
}
return {
status: 'DOWN',
latency: responseTime,
}
})
```
Using default status you can set a predefined status for the monitor. The predefined status can be `UP`, `DOWN`, or `DEGRADED`. If the monitor does not have api/dns/ping or if the monitor status is not updated using webbhook, manual incident this will the status of the monitor.
To view more examples of API monitors, please visit [here](/docs/monitors-examples)
## Category Name
## PING Monitor Attributes
The category name is used to group monitors. It is displayed on the status page and in the admin panel. It is optional. By a home category is already present. Adding monitor to the home category will show the monitor on the home page.
A list of attributes that can be used in PING monitors.
## Day Degraded Min Count
| Key | Required? | Explanation |
| ------------ | --------- | ----------------------------------------------------------------------- |
| ping.hostsV4 | Required | Array of hosts / IP to monitor ping response. Either domain name or IP4 |
| ping.hostsV6 | Required | Array of hosts / IP to monitor ping response. Either domain name or IP6 |
<span class="text-red-500 text-xs font-semibold">
REQUIRED
</span>
Either one of `hostsV4` or `hostsV6` is required
The day degraded minimum count is used to define the minimum number of degraded incidents in a day to mark the monitor as degraded for the day. A degraded day means if you are using Monitor Style as FULL under Theme, the whole bar will be shown as Degraded (yellow color) . It is optional. It has to be more than equal to 1
### Example
## Day Down Min Count
```yaml
- name: "Ping All"
description: "Ping All is where I ping all the hosts"
tag: "pingall"
defaultStatus: "UP"
ping:
hostsV4:
- "www.rajnandan.com"
- "103.125.217.243"
```
<span class="text-red-500 text-xs font-semibold">
REQUIRED
</span>
You can find more examples of PING monitors [here](/docs/monitor-examples#ping-monitor)
The day down minimum count is used to define the minimum number of down incidents in a day to mark the monitor as down for the day. A down day means if you are using Monitor Style as FULL under Theme, the whole bar will be shown as Down (red color) . It is optional. It has to be more than equal to 1
## DNS Monitor Attributes
## Include Degraded
A list of attributes that can be used in DNS monitors.
The include degraded is used to include the degraded checks in the total bad checks count. It is optional. By default uptime percentage is calculated as (UP+DEGRADED/UP+DEGRADED+DOWN). Setting it as `YES` will change the calculation to (UP/UP+DEGRADED+DOWN)
| Key | Required? | Explanation |
| ---------------- | --------- | ------------------------------------------------------------------------ |
| dns.hosts | Required | Array of hosts to monitor DNS response. Either domain name or IP4 or IP6 |
| dns.lookupRecord | Required | DNS record type. |
| dns.nameServer | Required | DNS server to use. |
| dns.matchType | Required | Match type for DNS response. Can be `ANY` or `ALL`. Default is `ALL` |
| dns.values | Required | Expected values for the DNS response. Array of string |
## Monitor Type
### Example
<span class="text-red-500 text-xs font-semibold">
REQUIRED
</span>
```yaml
- name: "DNS All"
description: "DNS All is where I check all the DNS"
tag: "dnsall"
defaultStatus: "UP"
dns:
host: "www.rajnandan.com"
lookupRecord: "CNAME"
nameServer: "8.8.8.8"
matchType: "ANY"
values:
- "rajnandan1.github.io"
```
### API
To monitor an API, you need to provide the URL of the API and the expected status code. If the status code is not received, the monitor will be marked as down. You can read more about API monitoring [here](/docs/monitors-api).
### DNS
To monitor the DNS records, you need to provide the domain name and the record type. If the record is not found, the monitor will be marked as down. You can read more about DNS monitoring [here](/docs/monitors-dns).
### PING
To monitor the ping, you need to provide the IP address or the domain name. If the ping is not successful, the monitor will be marked as down. You can read more about PING monitoring [here](/docs/monitors-ping).
### GAMEDIG
To monitor game/service, you need to provide the game or service to monitor, the IP address or the domain name and the port. If the query is not successful, the monitor will be marked as down. You can read more about GAMEDIG monitoring [here](/docs/monitors-gamedig).
---
## Triggers
To add a trigger to a monitor make sure you have created a trigger. You can read more about triggers [here](/docs/triggers).
- Click on the 🔔 icon on the top right corner of the monitor.
- Add details for either DOWN or DEGRADED.
- Failure Threshold(Required): The number of consecutive failures before the trigger is activated.
- Success Threshold(Required): The number of consecutive successes before the trigger is deactivated.
- Create Incident: Chose whether to create an incident or not when the trigger is activated. The incident will be created in Github. So make sure you have set up the Github token in the environment variables. The incident will be closed when the monitor is back to UP.
- Severity (Required): The severity of the incident. It can be `Critcal` or `Warning`.
- Custom Message (Required): Add your owner alert message.
- Choose Triggers: Choose the triggers you want to activate the trigger. You can choose multiple triggers.
- It will take upto 1 minute for the trigger to be activated.
---
## Edit Monitors
Click on the ⚙️ to edit the monitor.
### Deactivate Monitor
You can deactivate the monitor by switching the toggle to off. Deactivation a monitor will stop the monitor from running. It will take one minute to deactivate.
### Activate Monitor
You can activate the monitor by switching the toggle to on. Activation a monitor will start the monitor. It will take one minute to activate.
<div class="note info">
Any changes in the a live monitor will take 2 minutes to reflect.
</div>
## Supported Monitors
- [API Monitors](/docs/monitors-api)
- [DNS Monitors](/docs/monitors-dns)
- [PING Monitors](/docs/monitors-ping)
- [TCP Monitors](/docs/monitors-tcp)
- [SQL Monitors](/docs/monitors-sql)
- [SSL Monitors](/docs/monitors-ssl)
- [HEARTBEAT Monitors](/docs/monitors-heartbeat)
- [GROUP Monitors](/docs/monitors-group)
+48 -19
View File
@@ -1,41 +1,37 @@
---
title: Quick Start | Kener
title: Get started | Kener
description: Get started with Kener
---
# Quick Start
# Get Started
Here is a demonstration of how to get started with Kener in seconds
## Requirements
- Node.js Minimum version required is `v22.12.0`.
- Git
- sqlite3
- Node.js Minimum version required is `v18`.
- Git
- sqlite3
## Clone the repository
```shell
```bash
git clone https://github.com/rajnandan1/kener.git
cd kener
```
## Install Dependencies
```shell
```bash
npm install
```
## Setup Configuration
## Set up Environment Variables
- Rename `config/site.example.yaml` -> `config/site.yaml`
- Rename `config/monitors.example.yaml` -> `config/monitors.yaml`
- Rename `config/server.example.yaml` -> `config/server.yaml`
Kener needs some environment variables to be set to run properly. [Here](/docs/environment-vars) are the list of environment variables that you need to set.
```shell
cp config/site.example.yaml config/site.yaml
cp config/monitors.example.yaml config/monitors.yaml
cp config/server.example.yaml config/server.yaml
```bash
cp .env.example .env
```
## Start Kener
@@ -44,10 +40,43 @@ cp config/server.example.yaml config/server.yaml
npm run dev
```
Kener Development Server would be running at PORT 3000. Go to [http://localhost:3000](http://localhost:3000)
Kener Development Server would be running at PORT 3000. Go to [http://localhost:3000](http://localhost:3000) to see the Kener in action.
<div class="note info">
Status page will be at [http://localhost:3000](http://localhost:3000)
Status Page Manage Portal will be at [http://localhost:3000/manage/app/site](http://localhost:3000/manage/app/site)
</div>
## Create a new User
If this is the first time your are launching kener then you would be redirected to the [set up page](/manage/setup). Fill in the details and click on `Let's Go` button.
- **Name**: Your Name
- **Email**: Your Email
- **Password**: Your Password
Please note that the email should be a valid email address and password should be atleast 8 characters long with uppercase lowercase and numbers.
## Login
Once you have created the user, you can login with the credentials you have provided by going to the [login page](/manage/signin)
## Video Tutorial
You can watch the video tutorial on how to get started with Kener
[![Kener Video Tutorial](https://img.youtube.com/vi/4L9PnWPqMPw/0.jpg)](https://www.youtube.com/watch?v=4L9PnWPqMPw)
## Next Steps
- [Configure Site](/docs/customize-site)
- [Add Monitors](/docs/monitors)
- [Alerting](/docs/alerting)
Learn how to configure kener by going through one of the topics
- [Monitors](/docs/monitors): Learn how to set up and work with monitors in kener.
- [Triggers](/docs/triggers): Learn how to set up and work with triggers in kener.
- [Environment Variables](/docs/environment-vars): Learn how to set up and work with environment variables in kener.
- [API](/docs/kener-apis): Learn how to use the API in kener.
- [Databases](/docs/database): Learn how to set up and work with databases in kener.
- [Theme](/docs/theme): Learn how to set up and work with theme in kener.
+134
View File
@@ -0,0 +1,134 @@
---
title: Kener Role Based Access Control (RBAC)
description: Role-Based Access Control (RBAC) in Kener
---
# Role-Based Access Control (RBAC) in Kener
Kener includes a comprehensive role-based access control system that allows you to manage user permissions and access to various features. This document explains the available roles, their permissions, and how to manage users effectively.
## Available Roles
Kener offers three different roles with varying levels of permissions:
| Role | Description |
| ------ | ------------------------------------------------------------------------------ |
| Admin | Full access to all features including user management and system configuration |
| Editor | Can create and edit monitors, triggers, incidents, and other operational data |
| Member | Read-only access with limited interaction capabilities |
## Role Permissions
### Admin
Admins have unrestricted access to the entire system:
- **User Management**:
- Create, update, and deactivate users
- Change user roles
- Reset passwords for other users
- Send verification emails
- **System Configuration**:
- Configure all site settings
- Manage API keys
- Set up triggers and integrations
- **Operational Access**:
- Full access to create and manage monitors
- Create and update incidents
- Run tests on monitors and triggers
- Configure all notification channels
### Editor
Editors can manage most operational aspects but cannot administer users:
- **Content Management**:
- Create and edit monitors
- Configure monitor settings
- Create and manage triggers
- Create and update incidents
- Add incident updates and comments
- **Limited Access**:
- Cannot manage users
- Cannot change role assignments
- Cannot access certain system-level configurations
### Member
Members have read-only access with minimal interaction capabilities:
- **View Access**:
- View all monitors and their status
- View incidents and their history
- See system configuration (but cannot modify)
- **Limited Interactions**:
- Can test existing triggers but cannot create or edit them
- Cannot create or update incidents
- Cannot modify any system configuration
## Managing Users
### Adding New Users
Only Admins and Editors can add new users to the system:
1. Navigate to the **Users** page in the management dashboard
2. Click the **Add New User** button
3. Fill in the required information:
- Name
- Email
- Password
- Role (Member or Editor)
4. Click **Add User** to create the account
When a new user is added, a verification email can be sent to confirm their email address if email sending is configured.
### User Settings
Admins can manage user accounts through the user settings page:
- **Email Verification**: Send verification emails to users
- **Password Reset**: Reset a user's password
- **Role Management**: Change a user's role between Member and Editor
- **Account Status**: Activate or deactivate user accounts
### Best Practices
1. **Follow the Principle of Least Privilege**:
- Assign the minimum necessary permissions for users to perform their job
- Start users with the Member role and elevate as needed
2. **Regular Access Reviews**:
- Periodically review user access and roles
- Remove access for users who no longer need it
3. **Admin Accounts**:
- Limit the number of Admin accounts
- Use strong passwords for Admin accounts
- Consider using email verification for all users
## Role Limitations
- The Admin role can only be assigned during initial setup
- Members cannot create or modify content
- Users cannot modify their own role (only an Admin can change roles)
## Email Verification
When email sending is configured, users can verify their email addresses:
1. Admins can send verification emails from the user management interface
2. Users receive an email with a verification link
3. After clicking the link, the user's email is marked as verified
Email verification improves security and ensures that users have provided valid email addresses for notifications.
+14 -8
View File
@@ -20,18 +20,24 @@ description: Roadmap for Kener
~~Support for github enterprise~~
☐ Add support for fly.io deployment
☐ Add to digital ocean marketplace
☐ Add to aws marketplace
☐ Add Rate limiting to APIs
☐ Add support for custom themes
☐ Create Admin UI
~~Handle uploaded images for docker containers~~
~~Introduce sitemap.xml~~
~~Migrate from moment to date-fns for status page~~ (moment remains in dashboard. Will remove later)
~~Create Admin UI~~ Released in (3.0.0)
~~Webhook customization~~
☐ Monitor Update Screen
☐ i18n for admin dashboard
---
Request a feature or start a discussion [here](https://github.com/rajnandan1/kener/discussions/119)
Request a feature or start a discussion [here](https://github.com/rajnandan1/kener/discussions/new/choose)
+48
View File
@@ -0,0 +1,48 @@
---
title: SEO Setup | Kener
description: Learn how to set up and work with SEO in kener.
---
# SEO
SEO is important for your status page to be found on the internet. Kener provides you with the ability to set up SEO for your status page.
## Analytics
You can add Google Analytics, Amplitude, Mixpanel. Basic events and page tracking are supported.
### Google Analytics
ADD ID. It is the measurement ID that you get from Google Analytics. You can find it in the admin section of your Google Analytics account.
### Amplitude
Add API key of your Amplitude account
### Mixpanel
Add project token of your Mixpanel account.
### Plausible
- Add domain of your Plausible account. Example: `example.com`
- Add API key of your Plausible account. Default is `https://plausible.io/api/event`
- Add Script Source of your Plausible account. Default is `https://plausible.io/js/script.pageview-props.tagged-events.js`
### Microsoft Clarity
Add Project ID of your Microsoft Clarity account.
## Search Engine Optimization
You can add meta tags for SEO. You can add any `meta` that you want to add to the head of the page. Example: `og:image`, `og:title`, `og:description`, `twitter:image`, `twitter:title`, `twitter:description`.
Once added it will become
```html
<meta property="og:image" content="https://example.com/image.png" />
```
## Sitemap
Kener auto generates a sitemap for your status page. You can view it at `/sitemap.xml`
+52
View File
@@ -0,0 +1,52 @@
---
title: Site Info Setup | Kener
description: Learn how to set up and work with site info in kener.
---
# Site
Site info is used to define the site information that will be shown on the status page.
## Site Title
<span class="text-red-500 text-xs font-semibold">
REQUIRED
</span>
The site title is used to define the title of the site. It is required and has to be a string. It what will be shown on the browser tab. This will become
Example: `Kener - Open-Source and Modern looking Node.js Status Page for Effortless Incident Management`
```html
<title>
Kener - Open-Source and Modern looking Node.js Status Page for Effortless Incident Management
</title>
```
![Site Title](/documentation/ms_1.png)
## Site Name
<span class="text-red-500 text-xs font-semibold">
REQUIRED
</span>
This will be shown as a brand name on the status page on the nav bar top left.
![Site Name](/documentation/s_2.png)
## Home Location
<span class="text-red-500 text-xs font-semibold">
REQUIRED
</span>
Link to redirect to when Site Name or logo is clicked. Can be your main website. or it can be `/` to be current page. If you are using a base path it should be `/base-path` to keep the the user on the same page.
## Logo
Same as Site Name, it is placed left of Site Name as shown in image above
## Favicon
Favicon to be shown in browser tab.
+1 -1
View File
@@ -51,7 +51,7 @@ You can set custom label for the status badge. If you do not set a label it will
Shows the last health check was UP/DOWN/DEGRADED as SVG dot
```shell
```bash
http://[hostname]/badge/[tag]/dot
#or
http://[hostname]/badge/[tag]/dot?animate=ping
+195 -127
View File
@@ -1,129 +1,197 @@
{
"sidebar": [
{
"sectionTitle": "Getting Started",
"children": [
{
"title": "Introduction",
"link": "/docs/home",
"file": "/home.md"
},
{
"title": "Quick Start",
"link": "/docs/quick-start",
"file": "/docs/quick-start.md"
},
{
"title": "Changelogs",
"link": "/docs/changelogs",
"file": "/changelogs.md"
},
{
"title": "Roadmap",
"link": "/docs/roadmap",
"file": "/roadmap.md"
}
]
},
{
"sectionTitle": "Core Concepts",
"children": [
{
"title": "Customization (site.yaml)",
"link": "/docs/customize-site",
"file": "/customize-site.md"
},
{
"title": "Monitors (monitors.yaml)",
"link": "/docs/monitors",
"file": "/monitors.md"
},
{
"title": "Alerting (server.yaml)",
"link": "/docs/alerting",
"file": "/alerting.md"
},
{
"title": "Database (server.yaml)",
"link": "/docs/database",
"file": "/database.md"
},
{
"title": "Monitors Examples",
"link": "/docs/monitor-examples",
"file": "/monitor-examples.md"
},
{
"title": "Incident Management",
"link": "/docs/incident-management",
"file": "/incident-management.md"
},
{
"title": "Localization",
"link": "/docs/i18n",
"file": "/i18n.md"
},
{
"title": "Badges",
"link": "/docs/status-badges",
"file": "/status-badges.md"
},
{
"title": "Embed",
"link": "/docs/embed",
"file": "/embed.md"
},
{
"title": "Analytics",
"link": "/docs/site-analytics",
"file": "/site-analytics.md"
}
]
},
{
"sectionTitle": "Deployment",
"children": [
{
"title": "Environment Setup",
"link": "/docs/environment-vars",
"file": "/environment-vars.md"
},
{
"title": "Deployment",
"link": "/docs/deployment",
"file": "/deployment.md"
},
{
"title": "Github Setup",
"link": "/docs/gh-setup",
"file": "/gh-setup.md"
}
]
},
{
"sectionTitle": "API Reference",
"children": [
{
"title": "Kener APIs",
"link": "/docs/kener-apis",
"file": "/kener-apis.md"
}
]
},
{
"sectionTitle": "Guides",
"children": [
{
"title": "Categorize Monitors",
"link": "/docs/categorize-guide",
"file": "/categorize-guide.md"
},
{
"title": "Custom JS/CSS",
"link": "/docs/custom-js-css-guide",
"file": "/custom-js-css-guide.md"
}
]
}
]
"sidebar": [
{
"sectionTitle": "Getting Started",
"children": [
{
"title": "Introduction",
"link": "/docs/home",
"file": "/home.md"
},
{
"title": "Get Started",
"link": "/docs/quick-start",
"file": "/quick-start.md"
},
{
"title": "Concepts",
"link": "/docs/concepts",
"file": "/concepts.md"
},
{
"title": "Deployment",
"link": "/docs/deployment",
"file": "/deployment.md"
},
{
"title": "Databases",
"link": "/docs/database",
"file": "/database.md"
},
{
"title": "Access Control",
"link": "/docs/rbac",
"file": "/rbac.md"
}
]
},
{
"sectionTitle": "Guides",
"children": [
{
"title": "Setup Environment",
"link": "/docs/environment-vars",
"file": "/environment-vars.md"
},
{
"title": "Use Badges",
"link": "/docs/status-badges",
"file": "/status-badges.md"
},
{
"title": "Setup Monitors",
"link": "/docs/monitors",
"file": "/monitors.md"
},
{
"title": "Setup Triggers",
"link": "/docs/triggers",
"file": "/triggers.md"
},
{
"title": "Setup Site",
"link": "/docs/site",
"file": "/site.md"
},
{
"title": "Setup SEO",
"link": "/docs/seo",
"file": "/seo.md"
},
{
"title": "Setup Home",
"link": "/docs/home-page",
"file": "/home-page.md"
},
{
"title": "Setup Theme",
"link": "/docs/theme",
"file": "/theme.md"
},
{
"title": "View Alerts",
"link": "/docs/alerts",
"file": "/alerts.md"
},
{
"title": "API Keys",
"link": "/docs/apikeys",
"file": "/apikeys.md"
},
{
"title": "Incident Management",
"link": "/docs/incident-management",
"file": "/incident-management.md"
},
{
"title": "Embed",
"link": "/docs/embed",
"file": "/embed.md"
},
{
"title": "Custom JS/CSS",
"link": "/docs/custom-js-css-guide",
"file": "/custom-js-css-guide.md"
},
{
"title": "Internationalization",
"link": "/docs/i18n",
"file": "/i18n.md"
}
]
},
{
"sectionTitle": "Monitors",
"children": [
{
"title": "API/Website Monitor",
"link": "/docs/monitors-api",
"file": "/monitors-api.md"
},
{
"title": "Ping Monitor",
"link": "/docs/monitors-ping",
"file": "/monitors-ping.md"
},
{
"title": "TCP Monitor",
"link": "/docs/monitors-tcp",
"file": "/monitors-tcp.md"
},
{
"title": "DNS Monitor",
"link": "/docs/monitors-dns",
"file": "/monitors-dns.md"
},
{
"title": "Group Monitor",
"link": "/docs/monitors-group",
"file": "/monitors-group.md"
},
{
"title": "SSL Monitor",
"link": "/docs/monitors-ssl",
"file": "/monitors-ssl.md"
},
{
"title": "SQL Monitor",
"link": "/docs/monitors-sql",
"file": "/monitors-sql.md"
},
{
"title": "Heartbeat Monitor",
"link": "/docs/monitors-heartbeat",
"file": "/monitors-heartbeat.md"
},
{
"title": "Gamedig Monitor",
"link": "/docs/monitors-gamedig",
"file": "/monitors-gamedig.md"
}
]
},
{
"sectionTitle": "API Reference",
"children": [
{
"title": "Kener APIs",
"link": "/docs/kener-apis",
"file": "/kener-apis.md"
}
]
},
{
"sectionTitle": "Help",
"children": [
{
"title": "Fonts",
"link": "/docs/custom-fonts",
"file": "/custom-fonts.md"
},
{
"title": "Changelogs",
"link": "/docs/changelogs",
"file": "/changelogs.md"
},
{
"title": "Roadmap",
"link": "/docs/roadmap",
"file": "/roadmap.md"
}
]
}
]
}
+106
View File
@@ -0,0 +1,106 @@
---
title: Theme Customization | Kener
description: Learn how to customize the theme of your status page in kener.
---
# Theme Customization
Kener provides you with the ability to customize the theme of your status page. You can change the colors, fonts, and other styles of the status page.
---
## Home Page Pattern
Kener can show a subtle pattern in all your pages. It is either square or dots. Right now you cannot modify the color of the pattern. However, you can disable it by choosing none
---
## Default Theme
Kener comes with both dark and light theme. You can set a default theme that will be used every time the user visits.
Setting it to light or dark will make the theme light or dark respectively. Setting it to none will make the theme change based on the user's system preference. Setting it to system will make the theme change based on the user's system preference.
If you want to force a theme to be light or dark you can set it to light or dark and uncheck `Let Users toggle theme between light and dark`
---
## Monitor Style
You can change how the bars and summary of a monitor looks like.
### Status Bar
#### Partial
The status bar will be a gradient from green to red/yellow based on the status of the monitor.
![Trigger API](/documentation/x1.png)
#### Full
The status bar will be a solid color based on the status of the monitor.
![Trigger API](/documentation/x2.png)
---
### Roundness
Adjust the roundness of the status bar.
#### SHARP
![Trigger API](/documentation/x4.png)
#### ROUNDED
![Trigger API](/documentation/x3.png)
---
### Summary Type
Control how the summary of the monitor looks like.
#### Current
Shows the current live value of the monitor. Example: If it is DOWN currently it will say `DOWN for x minutes`, if it is UP it will say `Status Ok`
#### DAY
Shows the day's impact. Example: If it was down at 10:00 AM and it is 11:00 AM, it will say `DOWN for 1 hour`. If there was no DOWN or DEGRADED state it will say `Status Ok`
---
## Colors
You can change the colors of UP/DEGRADED/DOWN/MAINTENANCE states of the monitor. By default, it shows a shade of green, yellow, red and blue respectively.
---
## Font
You can change the font of the status page. You can choose from a list of google, or [bunny fonts](https://fonts.bunny.net/) (which is a fully GDPR-compliant, zero-tracking/no-logging CDN font service).
### Font URL
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 &#x3C;style&#x3E; tags.
</div>
+471
View File
@@ -0,0 +1,471 @@
---
title: Triggers | Kener
description: Learn how to set up and work with triggers in kener.
---
## Triggers
Triggers are used to trigger actions based on the status of your monitors. You can use triggers to send notifications, or call webhooks when a monitor goes down or up.
<div class="border rounded-md">
![Trigger API](/documentation/trig_1.png)
</div>
### Name
<span class="text-red-500 text-xs font-semibold">
REQUIRED
</span>
The name is used to define the name of the webhook. It is required and has to be a string.
### Description
The description is used to define the description of the webhook. It is optional and has to be a string.
Kener supports the following triggers:
- [Webhook](#webhook)
- [Discord](#discord)
- [Slack](#slack)
- [Email](#email)
### Trigger Variables
You can use the following variables in the triggers using mustache syntax.
| Key | Description | Mustache Variable |
| ------------- | ------------------------------------------------------------ | ----------------- |
| site_name | [string] Name of your site | {{site_name}} |
| logo_url | [string] Logo of your site | {{logo_url}} |
| site_url | [string] Url of your site | {{site_url}} |
| alert_name | [string] Name of the alert | {{alert_name}} |
| status | [string] Status of the alert. Can be `TRIGGERED`, `RESOLVED` | {{status}} |
| is_resolved | [bool] True if alert is `RESOLVED` | {{is_resolved}} |
| is_triggered | [bool] True if alert is `TRIGGERED` | {{is_triggered}} |
| description | [string] Description of the alert. | {{description}} |
| action_text | [string] CTA for next action on the alert | {{action_text}} |
| action_url | [string] URL for next action on the alert | {{action_url}} |
| metric | [string] Name of the monitor | {{metric}} |
| severity | [string] Severity of the alert. Can be `critical`, `warn` | {{severity}} |
| id | [string] Id of the alert | {{id}} |
| current_value | [number] Current count of failures | {{current_value}} |
| threshold | [number] Threshold Failue | {{threshold}} |
| source | [string] Source of the alert | {{source}} |
| timestamp | [string] Timestamp **ISO 8601** formatted | {{timestamp}} |
---
## Webhook
Webhook triggers are used to send a HTTP POST request to a URL when a monitor goes down or up.
<div class="border rounded-md">
![Trigger API](/documentation/trig_web.png)
</div>
### URL
<span class="text-red-500 text-xs font-semibold">
REQUIRED
</span>
The URL is used to define the URL of the webhook. It is required and has to be a valid URL.
You can also pass secrets that are set in the environment variables.
Example: `https://example.com/webhook?secret=$SECRET_X`. Make sure `$SECRET_X` is set in the environment variables.
### Method
Method will always be `POST`
### Headers
The headers are used to define the headers that should be sent with the request. It is optional and has to be a valid JSON object. You can add secrets that are set in the environment variables.
Example: `Authorization: Bearer $SECRET_Y`. Make sure `$SECRET_Y` is set in the environment variables.
While sending webhook kener will add two more headers: `Content-Type: application/json` and `User-Agent: Kener/3.0.0`.
### Body
Body of the webhook will be sent as below:
```json
{
"id": "mockoon-9",
"alert_name": "Mockoon DOWN",
"severity": "critical",
"status": "TRIGGERED",
"source": "Kener",
"timestamp": "2024-11-27T04:55:00.369Z",
"description": "🚨 **Service Alert**: Check the details below",
"details": {
"metric": "Mockoon",
"current_value": 1,
"threshold": 1
},
"actions": [
{
"text": "View Monitor",
"url": "https://kener.ing/monitor-mockoon"
}
]
}
```
### Custom Body
Using [mustache variables](#triggers-trigger-variables) you can customize the body of the webhook. If you are not using a json body then please make sure you are using the right content-type by setting custom headers.
### Examples
#### 1. Telegram
You can use the webhook trigger to send a message to a telegram channel. Enable `Use a custom webhook body`.
Set the URL to `https://api.telegram.org/bot[BOT_TOKEN]/sendMessage`. Replace [BOT_TOKEN] with your bot token.
```json
{
"chat_id": "[CHAT_ID]", // Replace [CHAT_ID] with your chat id
"text": "<b>{{alert_name}}</b>\n\n<b>Severity:</b> <code>{{severity}}</code>\n<b>Status:</b> {{status}}\n<b>Source:</b> Kener\n<b>Time:</b> {{timestamp}}\n\n📌 <b>Details:</b>\n- <b>Metric:</b>{{metric}}\n- <b>Current Value:</b> <code>{{current_value}}</code>\n- <b>Threshold:</b> <code>{{threshold}}</code>\n\n🔍 <a href=\"{{action_url}}\">{{action_text}}</a>",
"parse_mode": "HTML"
}
```
If you want to send a message to a group, then replace `[CHAT_ID]` with the group id.
You can also use environment variables to store the bot token and chat id. In that case the URL will be `https://api.telegram.org/bot$BOT_TOKEN/sendMessage`. In the body you can use `"chat_id": "$CHAT_ID"`. Make sure you have set the `BOT_TOKEN` and `CHAT_ID` in the <a href="/docs/environment-vars#secrets">environment variables</a>.
#### 2. Conditional Webhook
You can use `is_triggered` and `is_resolved` to create conditional webhooks.
```json
{
"title": "Monitor Alert for {{metric}} has been {{#is_triggered}}🔴 Triggered{{/is_triggered}}{{#is_resolved}}🟢 Resolved{{/is_resolved}}. Current value is {{current_value}} and threshold set is {{threshold}}"
}
```
The output will be
```json
{
"title": "Monitor Alert for Mockoon has been 🔴 Triggered. Current value is 1 and threshold set is 1"
}
```
```json
{
"title": "Monitor Alert for Mockoon has been 🟢 Resolved. Current value is 0 and threshold set is 1"
}
```
#### 3. Combing mustache with env variables
Combine the above example with `NODE_ENV` environment variable.
```json
{
"title": "Monitor Alert for {{metric}} in environment $NODE_ENV has been {{#is_triggered}}🔴 Triggered{{/is_triggered}}{{#is_resolved}}🟢 Resolved{{/is_resolved}}. Current value is {{current_value}} and threshold set is {{threshold}}."
}
```
---
## Discord
Discord triggers are used to send a message to a discord channel when a monitor goes down or up.
<div class="border rounded-md">
![Trigger API](/documentation/trig_2.png)
</div>
### Discord URL
<span class="text-red-500 text-xs font-semibold">
REQUIRED
</span>
The Discord URL is used to define the URL of the discord webhook. It is required and has to be a valid URL.
#### How to get the Discord URL?
1. Go to your discord server
2. Right-click on the channel you want to send the messages
3. Click on `Edit Channel`
4. Go to `Integrations`
5. Click on `Create Webhook`
6. Copy the URL
#### Discord Message
The discord message when alert is `TRIGGERED` will look like this
![Discord](/discord.png)
The discord message when alert is `RESOLVED` will look like this
![Discord](/documentation/discord_resolved.png)
### Modify Discord Message
You can modify the discord message by using [mustache variables](#triggers-trigger-variables).
```json
{
"username": "{{site_name}}",
"avatar_url": "{{{logo_url}}}",
"content": "## {{alert_name}}\n{{#is_triggered}}🔴 Triggered{{/is_triggered}}{{#is_resolved}}🟢 Resolved{{/is_resolved}}\n{{description}}\nClick [MY CTA]({{{action_url}}}) for more.",
"embeds": [
{
"title": "❌{{alert_name}}❌",
"description": "{{description}}",
"url": "{{{action_url}}}",
"color": "{{#is_triggered}}13250616{{/is_triggered}}{{#is_resolved}}5156244{{/is_resolved}}",
"fields": [
{
"name": "Monitor",
"value": "{{metric}}",
"inline": false
},
{
"name": "Severity",
"value": "{{severity}}",
"inline": false
},
{
"name": "Alert ID",
"value": "{{id}}",
"inline": false
},
{
"name": "Current Value",
"value": "{{current_value}}",
"inline": true
},
{
"name": "Threshold",
"value": "{{threshold}}",
"inline": true
}
],
"footer": {
"text": "{{source}}",
"icon_url": "{{{logo_url}}}"
},
"timestamp": "{{timestamp}}"
}
]
}
```
---
## Slack
Slack triggers are used to send a message to a slack channel when a monitor goes down or up.
<div class="border rounded-md">
![Trigger API](/documentation/trig_3.png)
</div>
### Slack URL
<span class="text-red-500 text-xs font-semibold">
REQUIRED
</span>
The Slack URL is used to define the URL of the slack webhook. It is required and has to be a valid URL.
#### How to get the Slack URL?
1. Go to your slack workspace
2. Click on `Apps` on the left sidebar
3. Search for `Incoming Webhooks`
4. Click on `Add to Slack`
5. Select the channel you want to send the messages
6. Click on `Add Incoming Webhook Integration`
7. Copy the URL
#### Slack Message
The slack message when alert is `TRIGGERED` will look like this
![Slack](/slack.png)
The slack message when alert is `RESOLVED` will look like this
![Slack](/documentation/slack_resolved.png)
### Modify Slack Message
You can modify the slack message by using [mustache variables](#triggers-trigger-variables).
```json
{
"blocks": [
{
"type": "header",
"text": {
"type": "plain_text",
"text": "Hello Alert {{alert_name}}",
"emoji": true
}
},
{
"type": "header",
"text": {
"type": "plain_text",
"text": "{{#is_triggered}}🔴 Triggered{{/is_triggered}}{{#is_resolved}}🟢 Resolved{{/is_resolved}}",
"emoji": true
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "{{description}}\n*Source:* {{source}}\n*Severity:* {{severity}}\n*Status:* {{status}}"
}
},
{
"type": "section",
"fields": [
{
"type": "mrkdwn",
"text": "*Metric:*\n{{metric}}"
},
{
"type": "mrkdwn",
"text": "*Current Value:*\n{{current_value}}"
},
{
"type": "mrkdwn",
"text": "*Threshold:*\n{{threshold}}"
},
{
"type": "mrkdwn",
"text": "*Environment:*\n$_NODE_ENV"
},
{
"type": "mrkdwn",
"text": "*Timestamp:*\n<!date^{{timestamp_unix}}^{date} at {time}|{{timestamp}}>"
}
]
},
{
"type": "actions",
"elements": [
{
"type": "button",
"text": {
"type": "plain_text",
"text": "{{action_text}}"
},
"url": "{{{action_url}}}",
"style": "primary"
}
]
}
]
}
```
---
## Email
Email triggers are used to send an email when a monitor goes down or up. Kener supports sending emails via [resend](https://resend.com) or over SMTP.
<div class="border rounded-md">
![Trigger API](/documentation/trig_4.png)
</div>
### Resend
To send emails using Resend you just need to set `RESEND_API_KEY` in the environment variables.
### SMTP
To send emails using SMTP, please enter
- Host: SMTP server host
- Port: SMTP server port
- User: SMTP server username
- Password: SMTP server password
<div class="note danger">
Since the password will be stored as plain text we encourage to use environment variables for the password. Let us say if you have an environment variable `SMTP_PASSWORD` then you can use it as `$SMTP_PASSWORD`.
</div>
<div class=" note info ">
If your SMTP provider does require username and password, you can set `SMTP_USER` and `SMTP_PASS` to `-`.
</div>
### To
<span class="text-red-500 text-xs font-semibold">
REQUIRED
</span>
The email addresses to which the email should be sent. It is required and has to be a valid email addresses. You can pass multiple email addresses separated by a comma.
### Sender
The email address from which the email should be sent.
It should be in the format `Name <email@address.com>`
If you have not connected your domain with resend, then use `Some Name <onboarding@resend.dev>`
### Subject
Subject of the email when `TRIGGERED`
```text
[TRIGGERED] Mockoon DOWN at 2024-12-27T04:42:01.430Z
```
Subject of the email when `RESOLVED`
```text
[RESOLVED] Mockoon DOWN at 2024-12-27T04:42:01.430Z
```
### Body
The emaik message when alert is `TRIGGERED` will look like this
![Slack](/documentation/em_t.png)
The emaik message when alert is `RESOLVED` will look like this
![Slack](/documentation/em_r.png)
---
## Edit Triggers
Click on the ⚙️ to edit the trigger.
### Deactivate Trigger
You can deactivate the trigger by switching the toggle to off. You cannot send message to a deactivated trigger. Any monitor with this trigger will not send any notifications.
---
+19
View File
@@ -0,0 +1,19 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
body {
background-color: #111;
}
</style>
</head>
<body>
<script
async
src="http://localhost:3000/embed/monitor-earth/js?theme=light&monitor=http://localhost:3000/embed/monitor-earth&locale=fr&tz=Asia/Tokyo"
></script>
</body>
</html>
+8
View File
@@ -0,0 +1,8 @@
#!/bin/sh
set -e
# Automatically set PUBLIC_WHITE_LABEL based on WHITE_LABEL
export PUBLIC_WHITE_LABEL="${WHITE_LABEL}"
# Replace shell with the given command (from CMD or runtime args)
exec "$@"
+2 -1
View File
@@ -9,7 +9,8 @@
"skipLibCheck": true,
"sourceMap": true,
"strict": true,
"moduleResolution": "bundler"
"moduleResolution": "bundler",
"noImplicitAny": false
}
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias and https://kit.svelte.dev/docs/configuration#files
//
+37
View File
@@ -0,0 +1,37 @@
// @ts-nocheck
import dotenv from "dotenv";
dotenv.config();
const databaseURL = process.env.DATABASE_URL || "sqlite://./database/kener.sqlite.db";
const databaseURLParts = databaseURL.split("://");
const databaseType = databaseURLParts[0];
const databasePath = databaseURLParts[1];
const knexOb = {
migrations: {
directory: "./migrations",
},
seeds: {
directory: "./seeds",
},
databaseType,
};
if (databaseType === "sqlite") {
knexOb.client = "better-sqlite3";
knexOb.connection = {
filename: databasePath,
};
knexOb.useNullAsDefault = true;
} else if (databaseType === "postgresql") {
knexOb.client = "pg";
knexOb.connection = databaseURL;
} else if (databaseType === "mysql") {
knexOb.client = "mysql2";
knexOb.connection = databaseURL;
} else {
console.error("Invalid database type");
process.exit(1);
}
export default knexOb;
+46 -7
View File
@@ -3,11 +3,18 @@ import { apiReference } from "@scalar/express-api-reference";
import dotenv from "dotenv";
dotenv.config();
import express from "express";
import sitemap from "./sitemap.js";
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";
const PORT = process.env.PORT || 3000;
const base = process.env.KENER_BASE_PATH || "";
const app = express();
const db = knex(knexOb);
app.use((req, res, next) => {
if (req.path.startsWith("/embed")) {
res.setHeader("Content-Security-Policy", "frame-ancestors *");
@@ -15,9 +22,19 @@ app.use((req, res, next) => {
res.setHeader("X-Powered-By", "Kener");
next();
});
app.get("/healthcheck", (req, res) => {
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
process.env.UPLOAD_PATH = "./uploads";
app.use(base + "/uploads", express.static("uploads"));
try {
const openapiJSON = fs.readFileSync("./openapi.json", "utf-8");
@@ -48,13 +65,35 @@ try {
} catch (e) {
console.warn("Error loading openapi.json, but that is okay.");
}
app.get("/sitemap.xml", (req, res) => {
res.setHeader("Content-Type", "application/xml");
res.end(sitemap);
});
app.use(handler);
app.listen(PORT, () => {
//migrations
async function runMigrations() {
try {
console.log("Running migrations...");
await db.migrate.latest(); // Runs migrations to the latest state
console.log("Migrations completed successfully!");
} catch (err) {
console.error("Error running migrations:", err);
}
}
//seed
async function runSeed() {
try {
console.log("Running seed...");
await db.seed.run(); // Runs seed to the latest state
console.log("Seed completed successfully!");
} catch (err) {
console.error("Error running seed:", err);
}
}
app.listen(PORT, async () => {
await runMigrations();
await runSeed();
await db.destroy();
Startup();
console.log("Kener is running on port " + PORT + "!");
});
+135
View File
@@ -0,0 +1,135 @@
// migrations/YYYYMMDDHHMMSS_create_monitoring_tables.js
export function up(knex) {
return (
knex.schema
// Create monitoring_data table
.createTable("monitoring_data", (table) => {
table.string("monitor_tag", 255).notNullable();
table.integer("timestamp").notNullable();
table.text("status");
table.float("latency", 8, 2);
table.text("type");
table.primary(["monitor_tag", "timestamp"]);
})
// Create monitor_alerts table
.createTable("monitor_alerts", (table) => {
table.increments("id").primary();
table.string("monitor_tag", 255).notNullable();
table.string("monitor_status", 255).notNullable();
table.string("alert_status", 255).notNullable();
table.integer("health_checks").notNullable();
table.integer("incident_number").defaultTo(0);
table.timestamp("created_at").defaultTo(knex.fn.now());
table.timestamp("updated_at").defaultTo(knex.fn.now());
})
// Add index to monitor_alerts table
.raw(
"CREATE INDEX idx_monitor_tag_created_at ON monitor_alerts (monitor_tag, created_at)"
)
.createTable("site_data", (table) => {
table.increments("id").primary();
table.string("key", 255).notNullable().unique();
table.text("value").notNullable();
table.string("data_type", 255).notNullable();
table.timestamp("created_at").defaultTo(knex.fn.now());
table.timestamp("updated_at").defaultTo(knex.fn.now());
})
.createTable("monitors", (table) => {
table.increments("id").primary();
table.string("tag", 255).notNullable().unique();
table.string("name", 255).notNullable().unique();
table.text("description");
table.text("image");
table.string("cron", 255);
table.string("default_status", 255);
table.string("status", 255);
table.string("category_name", 255);
table.string("monitor_type", 255);
table.string("down_trigger", 255);
table.string("degraded_trigger", 255);
table.text("type_data");
table.integer("day_degraded_minimum_count");
table.integer("day_down_minimum_count");
table.string("include_degraded_in_downtime", 255).defaultTo("NO");
table.timestamp("created_at").defaultTo(knex.fn.now());
table.timestamp("updated_at").defaultTo(knex.fn.now());
})
.createTable("triggers", (table) => {
table.increments("id").primary();
table.string("name", 255).notNullable().unique();
table.string("trigger_type", 255);
table.text("trigger_desc");
table.string("trigger_status", 255);
table.text("trigger_meta");
table.timestamp("created_at").defaultTo(knex.fn.now());
table.timestamp("updated_at").defaultTo(knex.fn.now());
})
.createTable("users", (table) => {
table.increments("id").primary();
table.string("email", 255).notNullable().unique();
table.string("name", 255).notNullable();
table.string("password_hash", 255).notNullable();
table.integer("is_active").defaultTo(1);
table.integer("is_verified").defaultTo(0);
table.string("role", 255).defaultTo("user");
table.timestamp("created_at").defaultTo(knex.fn.now());
table.timestamp("updated_at").defaultTo(knex.fn.now());
})
.createTable("api_keys", (table) => {
table.increments("id").primary();
table.string("name", 255).notNullable().unique();
table.string("hashed_key", 255).notNullable().unique();
table.string("masked_key", 255).notNullable();
table.string("status", 255).defaultTo("ACTIVE");
table.timestamp("created_at").defaultTo(knex.fn.now());
table.timestamp("updated_at").defaultTo(knex.fn.now());
})
.createTable("incidents", (table) => {
table.increments("id").primary();
table.string("title", 255).notNullable();
table.integer("start_date_time").notNullable();
table.integer("end_date_time");
table.timestamp("created_at").defaultTo(knex.fn.now());
table.timestamp("updated_at").defaultTo(knex.fn.now());
table.string("status", 255).defaultTo("ACTIVE");
table.string("state", 255).defaultTo("INVESTIGATING");
})
.createTable("incident_monitors", (table) => {
table.increments("id").primary();
table.string("monitor_tag", 255).notNullable();
table.string("monitor_impact", 255);
table.timestamp("created_at").defaultTo(knex.fn.now());
table.timestamp("updated_at").defaultTo(knex.fn.now());
table.integer("incident_id").notNullable();
table.unique(["monitor_tag", "incident_id"]);
})
.createTable("incident_comments", (table) => {
table.increments("id").primary();
table.text("comment").notNullable();
table.integer("incident_id").notNullable();
table.integer("commented_at").notNullable();
table.timestamp("created_at").defaultTo(knex.fn.now());
table.timestamp("updated_at").defaultTo(knex.fn.now());
table.string("status", 255).defaultTo("ACTIVE");
table.string("state", 255).defaultTo("INVESTIGATING");
})
);
}
export function down(knex) {
return (
knex.schema
// Drop tables in reverse order
.dropTableIfExists("monitor_alerts")
.dropTableIfExists("monitoring_data")
.dropTableIfExists("site_data")
.dropTableIfExists("monitors")
.dropTableIfExists("triggers")
.dropTableIfExists("users")
.dropTableIfExists("api_keys")
.dropTableIfExists("incidents")
.dropTableIfExists("incident_monitors")
.dropTableIfExists("incident_comments")
);
}
@@ -0,0 +1,11 @@
export function up(knex) {
return knex.schema.alterTable("incidents", function (table) {
table.text("incident_type").defaultTo("INCIDENT");
});
}
export function down(knex) {
return knex.schema.alterTable("incidents", function (table) {
table.dropColumn("incident_type");
});
}
@@ -0,0 +1,11 @@
export function up(knex) {
return knex.schema.alterTable("incidents", function (table) {
table.text("incident_source").defaultTo("DASHBOARD");
});
}
export function down(knex) {
return knex.schema.alterTable("incidents", function (table) {
table.dropColumn("incident_source");
});
}
@@ -0,0 +1,38 @@
/**
* @param { import("knex").Knex } knex
* @returns { Promise<void> }
*/
export function up(knex) {
return knex.schema.createTable("invitations", (table) => {
// Primary key
table.increments("id").primary();
// Core invitation fields
table.string("invitation_token").unique().notNullable();
table.string("invitation_type").notNullable();
table.integer("invited_user_id").nullable();
table.integer("invited_by_user_id").notNullable();
// Additional data fields
table.text("invitation_meta").nullable(); // For storing JSON or other metadata
table.timestamp("invitation_expiry").notNullable();
table.string("invitation_status").notNullable().defaultTo("PENDING");
// Timestamps
table.timestamp("created_at").defaultTo(knex.fn.now());
table.timestamp("updated_at").defaultTo(knex.fn.now());
// Indexes
table.index("invitation_status");
table.index("invitation_expiry");
table.index(["invited_by_user_id", "invitation_status"]);
});
}
/**
* @param { import("knex").Knex } knex
* @returns { Promise<void> }
*/
export function down(knex) {
return knex.schema.dropTableIfExists("invitations");
}
@@ -0,0 +1,49 @@
export function up(knex) {
return knex.schema
.createTable("subscribers", (table) => {
table.increments("id").primary();
table.string("subscriber_send").notNullable();
table.text("subscriber_meta").nullable();
table.string("subscriber_type").notNullable();
table.string("subscriber_status").notNullable();
table.datetime("created_at").defaultTo(knex.fn.now());
table.datetime("updated_at").defaultTo(knex.fn.now());
// Add unique constraint on subscriber_send and subscriber_type
table.unique(["subscriber_send", "subscriber_type"]);
// Add index on subscriber_send for better query performance
table.index(["subscriber_send"]);
})
.createTable("subscriptions", (table) => {
table.increments("id").primary();
table.integer("subscriber_id").unsigned().notNullable();
table.string("subscriptions_status").notNullable();
table.string("subscriptions_monitors").notNullable();
table.text("subscriptions_meta").nullable();
table.datetime("created_at").defaultTo(knex.fn.now());
table.datetime("updated_at").defaultTo(knex.fn.now());
// Add unique constraint on subscriber_id and subscriptions_monitors
// This constraint also creates an index that will be used for queries
table.unique(["subscriber_id", "subscriptions_monitors"]);
// Add index to optimize queries filtering by status and monitors
table.index(["subscriptions_status", "subscriptions_monitors"]);
})
.createTable("subscription_triggers", (table) => {
table.increments("id").primary();
table.string("subscription_trigger_type").notNullable().unique();
table.string("subscription_trigger_status").notNullable();
table.text("config").nullable();
table.datetime("created_at").defaultTo(knex.fn.now());
table.datetime("updated_at").defaultTo(knex.fn.now());
});
}
export function down(knex) {
return knex.schema
.dropTableIfExists("subscription_triggers")
.dropTableIfExists("subscriptions")
.dropTableIfExists("subscribers");
}
+1248 -219
View File
File diff suppressed because it is too large Load Diff
+312 -216
View File
@@ -1,9 +1,9 @@
---
info:
title: Kener API
version: 1.0.0
version: 3.0.0
description: |
# Kener Self-hosted node js status page
# Kener Selfhost node js status page
![Kener](https://kener.ing/newbg.png "kener")
API specification for Kener status page and incident management system. This API spec was created using [Frogment](https://www.frogment.app)
contact:
@@ -15,7 +15,7 @@ info:
url: https://opensource.org/licenses/MIT
openapi: 3.0.0
servers:
- url: https://your-kener-host.com
- url: https://{host}
description: Kener host URL
tags:
- name: Monitors
@@ -76,130 +76,138 @@ components:
uptime:
type: string
example: '100'
lastUpdatedAt:
last_updated_at:
type: integer
example: 1731251760
example:
status: UP
lastUpdatedAt: 1731251760
last_updated_at: 1731251760
uptime: '100'
Incident:
type: object
description: body of an incident
required:
- title
- tags
properties:
startDatetime:
start_date_time:
type: integer
description: UTC timestamp in seconds
example: 1731901920
endDatetime:
end_date_time:
type: integer
description: UTC timestamp in seconds
nullable: true
example: 1704123938
title:
type: string
example: Outage in mumbai
description: title of the incident
body:
created_at:
type: string
example: body of the incident
description: body of the incident
tags:
type: array
items:
type: string
example: earth
description: comma separated tags of monitors
example:
- earth
- google
impact:
example: '2025-01-09 04:12:01'
description: created at time
updated_at:
type: string
example: DOWN
description: Impact of the incident
example: '2025-01-09 06:10:00'
description: updated at time
status:
type: string
example: OPEN
description: delete or not deleted incident
enum:
- DOWN
- DEGRADED
isMaintenance:
type: boolean
description: is this incident because of maintenance
example: false
isIdentified:
type: boolean
description: has the incident been indentified
example: true
isResolved:
type: boolean
description: has the incident been resovled
example: true
- OPEN
- CLOSED
state:
type: string
description: the current status of the incident
example: INVESTIGATING
enum:
- INVESTIGATING
- IDENTIFIED
- MONITORING
- RESOLVED
example:
startDatetime: 1731901920
endDatetime: 1704123938
start_date_time: 1731901920
end_date_time: 1704123938
title: title of the incident
body: body of the incident
tags:
- earth
- google
impact: DOWN
isMaintenance: false
isIdentified: true
isResolved: true
created_at: '2025-01-09 04:12:01'
updated_at: '2025-01-09 04:12:01'
state: INVESTIGATING
status: OPEN
id: 4
IncidentResponse:
description: Incident response schema
allOf:
- "$ref": "#/components/schemas/Incident"
- type: object
properties:
createdAt:
type: integer
description: UTC timestamp in seconds, incident created at
example: 1731901920
closedAt:
type: integer
nullable: true
description: UTC timestamp in seconds, incident closed at
example: 1731901920
incidentNumber:
type: integer
description: id of the incident
example: 4
example:
createdAt: 1731901920
closedAt: 1731901920
incidentNumber: 4
Comment:
type: object
description: Comment of an incident
required:
- body
properties:
body:
comment:
type: string
example: comment 1
commented_at:
type: integer
example: 1736398336
state:
type: string
example: IDENTIFIED
example:
body: comment 2
commented_at: 1736398336
state: IDENTIFIED
CommentResponse:
type: object
description: Comment Response
properties:
commentID:
id:
type: integer
description: ID of the comment
example: 1873376745
body:
example: 60
comment:
type: string
description: body of the comment
example: comment 3
createdAt:
description: The content of the comment
example: Sometimes, you want all the goodness of moment#from but you don't
want to have to create two moments, you just want to display a length
of time.
incident_id:
type: integer
description: timestamp when comment was created
example: 1704123938
description: ID of the associated incident
example: 24
commented_at:
type: integer
description: Timestamp when the comment was added
example: 1736398336
created_at:
type: string
format: date-time
description: Timestamp when the comment was created in ISO 8601 format
example: '2025-01-09 04:52:16'
updated_at:
type: string
format: date-time
description: Timestamp when the comment was last updated in ISO 8601 format
example: '2025-01-09 04:52:16'
status:
type: string
description: Current status of the comment
example: ACTIVE
state:
type: string
description: the current status of the incident
example: INVESTIGATING
enum:
- INVESTIGATING
- IDENTIFIED
- MONITORING
- RESOLVED
example:
commentID: 1873376745
body: comment 4
createdAt: 1704123938
id: 60
comment: Sometimes, you want all the goodness of moment
incident_id: 24
commented_at: 1736398336
created_at: '2025-01-09 04:52:16'
updated_at: '2025-01-09 04:52:16'
status: ACTIVE
state: MONITORING
IncidentStatus:
type: object
description: Status of the incident
@@ -220,6 +228,25 @@ components:
isIdentified: true
isResolved: true
endDatetime: 1731901920
IncidentCreateRequest:
title: Incident Create Request Body
type: object
description: body of an incident
required:
- title
- start_date_time
properties:
start_date_time:
type: integer
description: UTC timestamp in seconds
example: 1731901920
title:
type: string
example: Outage in mumbai
description: title of the incident
example:
start_date_time: 1731901920
title: title of the incident
responses:
Response401:
description: Bad API keys response
@@ -267,102 +294,94 @@ components:
summary: create incident response
description: create incident response
value:
createdAt: 1731901920
closedAt: 1731901920
incidentNumber: 4
startDatetime: 1731901920
endDatetime: 1704123938
start_date_time: 1731901920
end_date_time: 1731901920
id: 4
created_at: '2025-01-09 04:12:01'
updated_at: '2025-01-10 04:12:01'
title: title of the incident
body: body of the incident
tags:
- earth
- google
impact: DOWN
isMaintenance: false
isIdentified: true
isResolved: true
status: OPEN
state: INVESTIGATING
CreateIncidentRequest:
summary: create incident request body
description: Some Description
description: create incident request body
value:
startDatetime: 1731901920
endDatetime: 1704123938
start_date_time: 1731901920
title: title of the incident
body: body of the incident
tags:
- earth
- google
impact: DOWN
isMaintenance: false
isIdentified: true
isResolved: true
SearchIncidentsResponse:
summary: array of incidents
description: Some Description
value:
- createdAt: 1731901920
closedAt: 1731901920
incidentNumber: 4
startDatetime: 1731901920
endDatetime: 1704123938
title: title of the incident
body: body of the incident
tags:
- earth
- google
impact: DOWN
isMaintenance: false
isIdentified: true
isResolved: true
- createdAt: 1731901920
closedAt: 1731901920
incidentNumber: 4
startDatetime: 1731901920
endDatetime: 1704123938
title: title of the incident
body: body of the incident
tags:
- earth
- google
impact: DOWN
isMaintenance: false
isIdentified: true
isResolved: true
- id: 2
title: future wala
start_date_time: 1736774486
end_date_time: 1737033787
created_at: '2025-01-12 13:21:32'
updated_at: '2025-01-12 13:23:15'
status: OPEN
state: RESOLVED
- id: 1
title: internatiinal asda
start_date_time: 1736684329
end_date_time:
created_at: '2025-01-12 12:18:53'
updated_at: '2025-01-12 13:21:32'
status: OPEN
state: INVESTIGATING
CommentsResponse:
summary: list of comments
description: Some Description
value:
- commentID: 1873376745
body: comment 4
createdAt: 1704123938
- commentID: 1873376745
body: comment 4
createdAt: 1704123938
- id: 58
comment: idensad
incident_id: 24
commented_at: 1736398295
created_at: '2025-01-09 04:51:37'
updated_at: '2025-01-09 04:51:37'
status: ACTIVE
state: IDENTIFIED
- id: 57
comment: Sometimes, you want all the goodness of moment#from but you don't
want to have to create two moments, you just want to display a length of
time.
incident_id: 24
commented_at: 1736398279
created_at: '2025-01-09 04:51:19'
updated_at: '2025-01-09 04:51:19'
status: ACTIVE
state: INVESTIGATING
CommentRequestBody:
summary: request body for a comment
description: request body for a comment to add in an incident
summary: request body for a update
description: request body for a update to add in an incident
value:
body: This is a comment
comment: This is a comment
commented_at: 1736398336
state: IDENTIFIED
CreateCommentResponse:
summary: create comment response body sample
description: create comment response body sample
summary: create update response body sample
description: create update response body sample
value:
commentID: 1873376745
body: comment 4
createdAt: 1704123938
id: 60
comment: Sometimes, you want all the goodness of moment
incident_id: 24
commented_at: 1736398336
created_at: '2025-01-09 04:52:16'
updated_at: '2025-01-09 04:52:16'
status: ACTIVE
state: MONITORING
paths:
"/api/status":
post:
operationId: updateMonitorStatus
summary: Update status of a monitor
description: Update status of an incident at a given timestamp
description: Update status of a monitor at a given timestamp UTC
tags:
- Monitors
security:
- bearerAuth: []
requestBody:
required: true
description: request body to update an incident
description: request body to update status of a monitor
content:
application/json:
schema:
@@ -403,7 +422,7 @@ paths:
- name: tag
in: query
required: true
description: monitor tag to get an incident
description: monitor tag to get status of it
schema:
type: string
example: earth
@@ -436,7 +455,7 @@ paths:
content:
application/json:
schema:
"$ref": "#/components/schemas/Incident"
"$ref": "#/components/schemas/IncidentCreateRequest"
examples:
sample:
"$ref": "#/components/examples/CreateIncidentRequest"
@@ -463,6 +482,17 @@ paths:
security:
- bearerAuth: []
parameters:
- name: status
in: query
description: status of the incident. Can be open or close. close means deleted
schema:
type: string
description: status of the incident. Can be open or close. close means deleted
enum:
- OPEN
- CLOSED
default: OPEN
example: OPEN
- name: state
in: query
description: state of the incident. Can be open or close
@@ -470,17 +500,12 @@ paths:
type: string
description: state of the incident. Can be open or close
enum:
- open
- closed
default: open
example: open
- name: tags
in: query
description: Comma separated monitor tags
schema:
type: string
description: Comma separated monitor tags
example: earth,google
- INVESTIGATING
- IDENTIFIED
- MONITORING
- RESOLVED
default: INVESTIGATING
example: IDENTIFIED
- name: page
in: query
description: page number
@@ -490,7 +515,7 @@ paths:
default: 1
minimum: 1
example: 1
- name: per_page
- name: limit
in: query
description: how many per page
schema:
@@ -499,27 +524,20 @@ paths:
description: how many per page
maximum: 100
example: 10
- name: created_after_utc
- name: start_date_time
description: start time
in: query
schema:
type: integer
description: start time
example: 1731866475
- name: created_before_utc
- name: end_date_time
description: end time
in: query
schema:
type: integer
description: end time
example: 1731866475
- name: title_like
description: title of the incident
in: query
schema:
type: string
description: title of the incident
example: outage
responses:
'200':
description: Search results retrieved successfully
@@ -536,19 +554,19 @@ paths:
"$ref": "#/components/responses/Response400"
'401':
"$ref": "#/components/responses/Response401"
"/api/incident/{incidentNumber}":
"/api/incident/{incident_id}":
parameters:
- name: incidentNumber
- name: incident_id
in: path
required: true
description: incident number as an integer to get incident by id
description: incident id as an integer to get incident by id
schema:
type: integer
example: 4
get:
operationId: getIncident
summary: Get an incident by number
description: API to get an incident by incident number
summary: Get an incident by id
description: API to get an incident by incident id
tags:
- Incidents
security:
@@ -570,7 +588,7 @@ paths:
patch:
operationId: updateIncident
summary: Update an incident
description: API to update an incident by incident number
description: API to update an incident by incident id
tags:
- Incidents
security:
@@ -599,26 +617,26 @@ paths:
"$ref": "#/components/responses/Response400"
'401':
"$ref": "#/components/responses/Response401"
"/api/incident/{incidentNumber}/comment":
"/api/incident/{incident_id}/updates":
parameters:
- name: incidentNumber
- name: incident_id
in: path
required: true
description: incident number to fetch comment for
description: incident id to fetch comment for
schema:
type: integer
example: 4
post:
operationId: addIncidentComment
summary: Add a comment to an incident
description: API to create comment for an incident
summary: Add an update to an incident
description: API to create update for an incident
tags:
- Incidents
security:
- bearerAuth: []
requestBody:
required: true
description: body to add a comment
description: body to add an update
content:
application/json:
schema:
@@ -642,15 +660,15 @@ paths:
"$ref": "#/components/responses/Response401"
get:
operationId: getIncidentComments
summary: Get comments for an incident
description: API to get comments for an incident
summary: Get updates for an incident
description: API to get updates for an incident
tags:
- Incidents
security:
- bearerAuth: []
responses:
'200':
description: Comments retrieved successfully
description: updates retrieved successfully
content:
application/json:
schema:
@@ -664,45 +682,123 @@ paths:
"$ref": "#/components/responses/Response400"
'401':
"$ref": "#/components/responses/Response401"
"/api/incident/{incidentNumber}/status":
"/api/incident/{incident_id}/monitors":
parameters:
- name: incidentNumber
- name: incident_id
in: path
required: true
description: incident number to fetch status for
description: incident id to fetch comment for
schema:
type: integer
example: 4
post:
operationId: updateIncidentStatus
summary: Update the status of an incident
description: API to update status of an incident
get:
summary: Get monitors for an incident
operationId: getIncidentMonitors
tags:
- Incidents
security:
- bearerAuth: []
requestBody:
parameters:
- name: incident_id
in: path
required: true
description: request body to update status of an incident
content:
application/json:
schema:
"$ref": "#/components/schemas/IncidentStatus"
example:
isIdentified: true
isResolved: true
endDatetime: 1731901920
schema:
type: integer
description: The ID of the incident
responses:
'200':
description: Incident status updated successfully
description: List of monitors for the incident
content:
application/json:
schema:
"$ref": "#/components/schemas/IncidentResponse"
examples:
success:
"$ref": "#/components/examples/CreateIncidentResponse"
'400':
"$ref": "#/components/responses/Response400"
'401':
"$ref": "#/components/responses/Response401"
type: array
items:
type: object
properties:
monitor_tag:
type: string
description: The tag of the monitor
example: earth
monitor_impact:
type: string
description: The impact status of the monitor
example: DOWN
security:
- bearerAuth: []
post:
summary: Add a monitor to an incident
operationId: addIncidentMonitor
tags:
- Incidents
parameters:
- name: incident_id
in: path
required: true
schema:
type: integer
description: The ID of the incident
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
tag:
type: string
description: The tag for the monitor
example: okbookmarks
impact:
type: string
description: The impact status of the monitor
example: DEGRADED
responses:
'200':
description: Monitor added successfully
content:
application/json:
schema:
type: array
items:
type: object
properties:
monitor_tag:
type: string
description: The tag of the monitor
example: okbookmarks
monitor_impact:
type: string
description: The impact status of the monitor
example: DEGRADED
security:
- bearerAuth: []
delete:
summary: Delete a monitor from an incident
operationId: deleteIncidentMonitor
tags:
- Incidents
parameters:
- name: incident_id
in: path
required: true
schema:
type: integer
description: The ID of the incident
- name: tag
in: query
required: true
schema:
type: string
description: The tag of the monitor to delete
responses:
'200':
description: Monitor deleted successfully
content:
application/json:
schema:
type: object
properties:
message:
type: string
description: Confirmation message
example: Monitor deleted successfully
security:
- bearerAuth: []
+9529 -6330
View File
File diff suppressed because it is too large Load Diff
+124 -94
View File
@@ -1,96 +1,126 @@
{
"name": "kener",
"version": "2.0.0",
"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.",
"author": "Raj Nandan Sharma <rajnandan1@gmail.com>",
"keywords": [
"Node.js application",
"Open-source status page",
"Service monitoring tool",
"Real-time incident management",
"Customizable reporting",
"Service outage tracker",
"User-friendly dashboard",
"Incident communication platform",
"Scalable monitoring solution",
"Community-driven software",
"Website status tracker",
"Incident response tool",
"System status monitoring",
"Service reliability management",
"Incident alert system"
],
"repository": {
"type": "git",
"url": "https://github.com/rajnandan1/kener.git"
},
"scripts": {
"build": "vite build",
"preview": "vite preview",
"predevschedule": "node build.js",
"configure": "node build.js",
"devschedule": "node src/lib/server/startup.js",
"schedule": "node src/lib/server/startup.js",
"predevelopment": "node delay.js",
"development": "vite dev",
"dev": "npm-run-all --parallel devschedule development",
"prettify": "prettier --write .",
"start": "node main.js",
"prod": "npm-run-all --parallel schedule start"
},
"devDependencies": {
"@sveltejs/adapter-auto": "^2.0.0",
"@sveltejs/adapter-node": "^1.3.1",
"@sveltejs/kit": "^1.27.4",
"@tailwindcss/typography": "^0.5.10",
"autoprefixer": "^10.4.14",
"concurrently": "^8.2.2",
"cross-env": "^7.0.3",
"postcss": "^8.4.24",
"postcss-load-config": "^4.0.1",
"prettier": "^3.2.5",
"prettier-plugin-svelte": "^3.2.3",
"prettier-plugin-tailwindcss": "^0.5.14",
"svelte": "^4.0.5",
"svelte-check": "^3.6.0",
"tailwindcss": "^3.3.2",
"typescript": "^5.0.0",
"vite": "^4.4.2"
},
"type": "module",
"dependencies": {
"@formkit/auto-animate": "^0.8.2",
"@number-flow/svelte": "^0.2.1",
"@scalar/express-api-reference": "^0.4.167",
"analytics": "^0.8.14",
"axios": "^1.6.2",
"badge-maker": "^3.3.1",
"better-sqlite3": "^11.5.0",
"bits-ui": "^0.9.9",
"clsx": "^2.0.0",
"croner": "^7.0.5",
"dns2": "^2.1.0",
"dotenv": "^16.4.5",
"express": "^4.18.2",
"figlet": "^1.8.0",
"fs-extra": "^11.1.1",
"js-yaml": "^4.1.0",
"lucide-svelte": "^0.292.0",
"marked": "^11.1.1",
"mode-watcher": "^0.4.1",
"moment": "^2.29.4",
"moment-timezone": "^0.5.43",
"node-cache": "^5.1.2",
"npm-run-all": "^4.1.5",
"pg": "^8.13.1",
"pg-pool": "^3.7.0",
"ping": "^0.4.4",
"queue": "^7.0.0",
"randomstring": "^1.3.0",
"svelte-legos": "^0.2.5",
"tailwind-merge": "^2.0.0",
"tailwind-variants": "^0.1.18"
}
"name": "kener",
"version": "3.2.18",
"private": false,
"license": "MIT",
"description": "Kener is a modern, open-source status page application built with Node.js. It provides real-time monitoring, uptime tracking, incident management, and beautiful dashboards. Perfect for DevOps teams, SaaS providers, and businesses needing reliable service status communication with minimal setup.",
"author": "Raj Nandan Sharma <rajnandan1@gmail.com>",
"keywords": [
"status page",
"uptime monitoring",
"incident management",
"DevOps tools",
"service reliability",
"open source",
"Node.js",
"dashboard",
"system monitoring",
"status alerts",
"outage communication",
"API monitoring",
"SaaS status",
"performance metrics",
"status reporting"
],
"repository": {
"type": "git",
"url": "https://github.com/rajnandan1/kener.git"
},
"scripts": {
"build": "vite build",
"preview": "vite preview",
"configure": "node build.js",
"migrate": "npx knex migrate:latest",
"preseed": "npx knex migrate:latest",
"seed": "npx knex seed:run",
"predev": "npm run seed",
"devschedule": "node src/lib/server/startup.js",
"schedule": "node src/lib/server/startup.js",
"development": "vite dev",
"generate-readme": "node scripts/generate-readme.js",
"dev": "npm-run-all --parallel devschedule development",
"prettify": "prettier --write .",
"start": "node main.js"
},
"devDependencies": {
"@sveltejs/adapter-auto": "^2.0.0",
"@sveltejs/adapter-node": "^1.3.1",
"@sveltejs/kit": "^1.27.4",
"@tailwindcss/typography": "^0.5.10",
"@zerodevx/svelte-toast": "^0.9.6",
"autoprefixer": "^10.4.14",
"concurrently": "^8.2.2",
"cross-env": "^7.0.3",
"date-picker-svelte": "^2.15.1",
"postcss": "^8.4.24",
"postcss-load-config": "^4.0.1",
"prettier": "^3.2.5",
"prettier-plugin-svelte": "^3.2.3",
"prettier-plugin-tailwindcss": "^0.5.14",
"svelte": "^4.0.5",
"svelte-awesome-color-picker": "^3.1.4",
"svelte-check": "^3.6.0",
"svelte-dnd-action": "^0.9.55",
"tailwindcss": "^3.3.2",
"typescript": "^5.0.0",
"vite": "^4.4.2"
},
"type": "module",
"dependencies": {
"@babel/runtime": "^7.26.10",
"@codemirror/lang-css": "^6.3.1",
"@codemirror/lang-html": "^6.4.9",
"@codemirror/lang-javascript": "^6.2.3",
"@codemirror/lang-json": "^6.0.1",
"@codemirror/lang-markdown": "^6.3.2",
"@formkit/auto-animate": "^0.8.2",
"@number-flow/svelte": "^0.2.1",
"@scalar/express-api-reference": "^0.4.167",
"@uiw/codemirror-theme-github": "^4.23.10",
"analytics": "^0.8.14",
"axios": "^1.6.2",
"badge-maker": "^3.3.1",
"bcrypt": "^5.1.1",
"better-sqlite3": "^11.5.0",
"bits-ui": "^0.9.9",
"cheerio": "^1.0.0",
"clsx": "^2.0.0",
"croner": "^7.0.5",
"date-fns": "^4.1.0",
"date-fns-tz": "^3.2.0",
"dns2": "^2.1.0",
"dotenv": "^16.4.5",
"express": "^4.18.2",
"figlet": "^1.8.0",
"fs-extra": "^11.1.1",
"gamedig": "^5.3.0",
"js-yaml": "^4.1.0",
"jsonwebtoken": "^9.0.2",
"knex": "^3.1.0",
"lucide-svelte": "^0.483.0",
"marked": "^11.1.1",
"mobile-detect": "^1.4.5",
"mode-watcher": "^0.4.1",
"moment": "^2.29.4",
"moment-timezone": "^0.5.43",
"mustache": "^4.2.0",
"mysql2": "^3.12.0",
"node-cache": "^5.1.2",
"nodemailer": "^6.10.0",
"npm-run-all": "^4.1.5",
"pg": "^8.13.1",
"pg-pool": "^3.7.0",
"ping": "^0.4.4",
"queue": "^7.0.0",
"randomstring": "^1.3.0",
"resend": "^4.0.1",
"svelte-codemirror-editor": "^1.4.1",
"svelte-legos": "^0.2.5",
"tailwind-merge": "^2.0.0",
"tailwind-variants": "^0.1.18",
"vite-plugin-package-version": "^1.1.0"
},
"engines": {
"node": ">=20.0.0"
}
}
+31
View File
@@ -0,0 +1,31 @@
import fs from "fs";
import Mustache from "mustache";
import dotenv from "dotenv";
import { fileURLToPath } from "url";
import path from "path";
// Load environment variables from .env file
dotenv.config();
// Resolve paths correctly in ES modules
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// Load the template
const templatePath = path.resolve(__dirname, "../README.template.md");
const template = fs.readFileSync(templatePath, "utf-8");
// Load environment variables and provide default values
const data = {
kener_full_version: process.env.BUILD_FULL_VERSION || "N/A",
kener_major_version: process.env.BUILD_MAJOR_VERSION || "N/A",
kener_major_minor_version: process.env.BUILD_MAJOR_MINOR_VERSION || "N/A",
};
// Render README.md
const output = Mustache.render(template, data);
// Write to README.md
fs.writeFileSync(path.resolve(__dirname, "../README.md"), output);
console.log("✅ README.md generated successfully!");
+32
View File
@@ -0,0 +1,32 @@
import monitorSeed from "../src/lib/server/db/seedMonitorData.js";
/**
* @param { import("knex").Knex } knex
* @returns { Promise<void> }
*/
export async function seed(knex) {
// Check if the table is empty
const count = await knex("monitors").count("id as CNT").first();
if (count.CNT == 0) {
// Deletes ALL existing entries
for (const monitor of monitorSeed) {
await knex("monitors").insert({
tag: monitor.tag,
name: monitor.name,
description: monitor.description,
image: monitor.image,
cron: monitor.cron,
default_status: monitor.default_status,
status: monitor.status,
category_name: monitor.category_name,
monitor_type: monitor.monitor_type,
type_data: monitor.type_data,
day_degraded_minimum_count: monitor.day_degraded_minimum_count,
day_down_minimum_count: monitor.day_down_minimum_count,
include_degraded_in_downtime: monitor.include_degraded_in_downtime,
created_at: knex.fn.now(),
updated_at: knex.fn.now()
});
}
}
}
+24
View File
@@ -0,0 +1,24 @@
import seedSiteData from "../src/lib/server/db/seedSiteData.js";
/**
* @param { import("knex").Knex } knex
* @returns { Promise<void> }
*/
export async function seed(knex) {
// Check if the table is empty
const count = await knex("site_data").count("id as CNT").first();
for (const key in seedSiteData) {
if (Object.prototype.hasOwnProperty.call(seedSiteData, key)) {
let value = seedSiteData[key];
let data_type = typeof value;
if (data_type === "object") {
value = JSON.stringify(value);
}
const existingEntry = await knex("site_data").where({ key: key }).first();
if (!existingEntry) {
await knex("site_data").insert([{ key: key, value: value, data_type: data_type }]);
}
}
}
}
-45
View File
@@ -1,45 +0,0 @@
//read from process.env.PUBLIC_KENER_FOLDER / site.json
//read from process.env.PUBLIC_KENER_FOLDER / monitors.json
// create sitemap.xml
import fs from "fs-extra";
let siteMap = "";
import dotenv from "dotenv";
dotenv.config();
const site = fs.readJSONSync("./database/site.json", "utf8");
const monitors = fs.readJSONSync("./database/monitors.json", "utf8");
if (site.siteURL !== undefined && site.siteURL !== null && site.siteURL !== "") {
if (monitors.length > 0) {
siteMap = `<?xml version="1.0" encoding="UTF-8"?>
<urlset
xmlns="https://www.sitemaps.org/schemas/sitemap/0.9"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://www.sitemaps.org/schemas/sitemap/0.9
https://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">
${monitors
.map((monitor) => {
return `
<url>
<loc>${site.siteURL}/incident/${monitor.folderName}</loc>
<lastmod>${new Date().toISOString()}</lastmod>
<changefreq>daily</changefreq>
<priority>0.8</priority>
</url>`;
})
.join("\n")}
${monitors
.map((monitor) => {
return `
<url>
<loc>${site.siteURL}/monitor-${encodeURIComponent(monitor.tag)}</loc>
<lastmod>${new Date().toISOString()}</lastmod>
<changefreq>daily</changefreq>
<priority>0.8</priority>
</url>`;
})
.join("\n")}
</urlset>`;
}
}
//export default siteMap
export default siteMap;
-2
View File
@@ -3,8 +3,6 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
%sveltekit.head%
</head>
+64 -56
View File
@@ -1,82 +1,90 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@import "tailwindcss/base";
@import "tailwindcss/components";
@import "tailwindcss/utilities";
@layer base {
:root {
--background: 0 0% 100%;
--background-kener: hsl(0, 0%, 100%);
--background-kener-rgba: rgba(255, 255, 255, 0.5);
--foreground: 240 10% 4%;
:root {
--background: 0 0% 100%;
--background-kener: hsl(0, 0%, 100%);
--background-kener-rgba: rgba(255, 255, 255, 0.5);
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--foreground: 240 10% 4%;
--popover: 0 0% 100%;
--popover-foreground: 240 10% 4%;
--muted: 0 0% 92.9%;
--muted-foreground: 215.4 16.3% 46.9%;
--card: 0 0% 100%;
--card-foreground: 240 10% 4%;
--popover: 0 0% 100%;
--popover-foreground: 240 10% 4%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--card: 0 0% 99%;
--card-foreground: 240 10% 4%;
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;
--date-picker-background: hsl(0 0% 99%);
--date-picker-foreground: hsl(240 10% 4%);
--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;
--destructive: 0 72.2% 50.6%;
--destructive-foreground: 210 40% 98%;
--secondary: 0 0% 92.9%;
--secondary-foreground: 222.2 47.4% 11.2%;
--ring: 240 10% 4%;
--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;
--radius: 0.5rem;
}
--destructive: 0 72.2% 50.6%;
--destructive-foreground: 210 40% 98%;
.dark {
--background: 240 10% 4%;
--background-kener: hsl(240, 10%, 4%);
--background-kener-rgba: rgba(9, 9, 11, 0.35);
--foreground: 210 40% 98%;
--ring: 240 10% 4%;
--muted: 240 4% 16%;
--muted-foreground: 215 20.2% 65.1%;
--radius: 0.5rem;
}
--popover: 240 10% 4%;
--popover-foreground: 210 40% 98%;
.dark {
--background: 240 7% 11%;
--background-kener: hsl(240 7% 11%);
--background-kener-rgba: rgba(30, 30, 26, 0.35);
--foreground: 60 10% 86%;
--card: 240 10% 4%;
--card-foreground: 210 40% 98%;
--muted: 240 4% 16%;
--muted-foreground: 0 0% 40%;
--border: 240 4% 16%;
--input: 240 4% 16%;
--popover: 230 9% 14%;
--popover-foreground: 210 40% 98%;
--primary: 210 40% 98%;
--primary-foreground: 222.2 47.4% 11.2%;
--card: 230 9% 14%;
--card-foreground: 60 10% 86%;
--secondary: 240 4% 16%;
--secondary-foreground: 210 40% 98%;
--border: 60 1% 17%;
--input: 60 6% 17%;
--accent: 240 4% 16%;
--accent-foreground: 210 40% 98%;
--date-picker-background: hsl(240 4% 16%);
--date-picker-foreground: hsl(60 10% 86%);
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 40% 98%;
--primary: 60 10% 86%;
--primary-foreground: 222.2 47.4% 11.2%;
--ring: hsl(212.7, 26.8%, 83.9);
}
--secondary: 240 1% 18%;
--secondary-foreground: 60 10% 86%;
--accent: 240 4% 16%;
--accent-foreground: 210 40% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 40% 98%;
--ring: hsl(212.7, 26.8%, 83.9);
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}
+67 -12
View File
@@ -1,24 +1,79 @@
code:not([class^="language-"]) {
@apply rounded bg-gray-100 px-1.5 py-0.5 font-mono text-xs dark:bg-gray-800;
}
.sidebar-item.active,
.sidebar-item:hover {
color: #ed702d;
color: #ed702d;
}
.w-585px {
width: 585px;
width: 585px;
}
main {
scroll-behavior: smooth;
scroll-behavior: smooth;
}
.kener-home-links a {
text-decoration: none;
background-color: var(--bg-background);
text-decoration: none;
background-color: var(--bg-background);
}
.kener-home-links > div:hover {
transition: all 0.3s;
box-shadow: 0 0 8px 1.5px #3e9a4b;
.kener-home-links > a:hover {
transition: all 0.3s;
border-color: #ed702d;
background-color: #ed702d;
}
.accm input:checked ~ div {
display: block;
}
.accm input ~ div {
display: none;
}
.accm input {
visibility: hidden;
}
.accmt {
background-color: hsl(223, 10%, 14%);
}
.accm input:checked ~ .showaccm span:first-child {
display: none;
}
.accm input:checked ~ .showaccm span:last-child {
display: block;
}
.accm input ~ .showaccm span:first-child {
display: block;
}
.accm input ~ .showaccm span:last-child {
display: none;
}
.hljs {
background: transparent !important;
}
.note {
@apply mt-4 rounded-md border bg-background p-3 text-sm shadow-sm;
}
.note.danger {
border: 1px solid #e3342f;
color: #e3342f;
}
.note.info {
border: 1px solid #3490dc;
color: #3490dc;
}
.copybtn .copy-btn {
transform: scale(1);
}
.copybtn .check-btn {
transform: scale(0);
}
.copybtn:focus .copy-btn {
transform: scale(0);
}
.copybtn:focus .check-btn {
transform: scale(1);
}
+90
View File
@@ -0,0 +1,90 @@
@font-face {
font-family: "Lato";
src:
url("./fonts/lato/latin_subset/lato-hairline.woff2") format("woff2"),
url("./fonts/lato/latin_subset/lato-hairline.woff") format("woff"),
url("./fonts/lato/latin_subset/lato-hairline.ttf") format("truetype");
font-weight: 100;
font-style: normal;
}
@font-face {
font-family: "Lato";
src:
url("./fonts/lato/latin_subset/lato-hairline-italic.woff2") format("woff2"),
url("./fonts/lato/latin_subset/lato-hairline-italic.woff") format("woff"),
url("./fonts/lato/latin_subset/lato-hairline-italic.ttf") format("truetype");
font-weight: 100;
font-style: italic;
}
@font-face {
font-family: "Lato";
src:
url("./fonts/lato/latin_subset/lato-light.woff2") format("woff2"),
url("./fonts/lato/latin_subset/lato-light.woff") format("woff"),
url("./fonts/lato/latin_subset/lato-light.ttf") format("truetype");
font-weight: 300;
font-style: normal;
}
@font-face {
font-family: "Lato";
src:
url("./fonts/lato/latin_subset/lato-light-italic.woff2") format("woff2"),
url("./fonts/lato/latin_subset/lato-light-italic.woff") format("woff"),
url("./fonts/lato/latin_subset/lato-light-italic.ttf") format("truetype");
font-weight: 300;
font-style: italic;
}
@font-face {
font-family: "Lato";
src:
url("./fonts/lato/latin_subset/lato-regular.woff2") format("woff2"),
url("./fonts/lato/latin_subset/lato-regular.woff") format("woff"),
url("./fonts/lato/latin_subset/lato-regular.ttf") format("truetype");
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: "Lato";
src:
url("./fonts/lato/latin_subset/lato-regular-italic.woff2") format("woff2"),
url("./fonts/lato/latin_subset/lato-regular-italic.woff") format("woff"),
url("./fonts/lato/latin_subset/lato-regular-italic.ttf") format("truetype");
font-weight: normal;
font-style: italic;
}
@font-face {
font-family: "Lato";
src:
url("./fonts/lato/latin_subset/lato-bold.woff2") format("woff2"),
url("./fonts/lato/latin_subset/lato-bold.woff") format("woff"),
url("./fonts/lato/latin_subset/lato-bold.ttf") format("truetype");
font-weight: 700;
font-style: normal;
}
@font-face {
font-family: "Lato";
src:
url("./fonts/lato/latin_subset/lato-bold-italic.woff2") format("woff2"),
url("./fonts/lato/latin_subset/lato-bold-italic.woff") format("woff"),
url("./fonts/lato/latin_subset/lato-bold-italic.ttf") format("truetype");
font-weight: 700;
font-style: italic;
}
@font-face {
font-family: "Lato";
src:
url("./fonts/lato/latin_subset/lato-black.woff2") format("woff2"),
url("./fonts/lato/latin_subset/lato-black.woff") format("woff"),
url("./fonts/lato/latin_subset/lato-black.ttf") format("truetype");
font-weight: 900;
font-style: normal;
}
@font-face {
font-family: "Lato";
src:
url("./fonts/lato/latin_subset/lato-black-italic.woff2") format("woff2"),
url("./fonts/lato/latin_subset/lato-black-italic.woff") format("woff"),
url("./fonts/lato/latin_subset/lato-black-italic.ttf") format("truetype");
font-weight: 900;
font-style: italic;
}
+6
View File
@@ -0,0 +1,6 @@
/** @type {import('@sveltejs/kit').Handle} */
export async function handle({ event, resolve }) {
const response = await resolve(event);
response.headers.delete("Link");
return response;
}
+833 -235
View File
File diff suppressed because it is too large Load Diff
+100
View File
@@ -0,0 +1,100 @@
<script>
import { onMount, onDestroy, beforeUpdate } from "svelte";
import { createEventDispatcher } from "svelte";
const dispatch = createEventDispatcher();
export let monitor;
export let theme = "light";
export let bgc = "transparent";
export let locale = "en";
let iframe, listeners;
let containerId = `embed-container-${Date.now()}-${Math.random().toString(36).substr(2, 5)}`;
let isMounted = false;
let prevMonitor, prevTheme, prevBgc, prevLocale;
onMount(() => {
if (!monitor) return;
isMounted = true;
prevMonitor = monitor;
prevTheme = theme;
prevBgc = bgc;
prevLocale = locale;
const container = document.getElementById(containerId);
if (!container) return;
const uid = "KENER_" + ~~(new Date().getTime() / 86400000);
const uriEmbedded = `${monitor}?theme=${theme}&bgc=${bgc}&locale=${locale}`;
iframe = document.createElement("iframe");
iframe.src = uriEmbedded;
iframe.id = uid;
iframe.width = "0%";
iframe.height = "0";
iframe.frameBorder = "0";
iframe.allowTransparency = true;
iframe.sandbox =
"allow-modals allow-forms allow-same-origin allow-scripts allow-popups allow-top-navigation-by-user-activation allow-downloads";
iframe.allow = "midi; geolocation; microphone; camera; display-capture; encrypted-media;";
container.appendChild(iframe);
const setHeight = (data) => {
if (data.height !== undefined) {
iframe.height = data.height;
}
};
const setWidth = (data) => {
if (data.width !== undefined) {
iframe.width = data.width;
}
};
listeners = (event) => {
if (event.data && event.data.height !== undefined) {
setHeight(event.data);
}
if (event.data && event.data.width !== undefined) {
setWidth(event.data);
}
};
window.addEventListener("message", listeners, false);
});
beforeUpdate(() => {
if (
isMounted &&
iframe &&
(monitor !== prevMonitor || theme !== prevTheme || bgc !== prevBgc || locale !== prevLocale)
) {
iframe.src = `${monitor}?theme=${theme}&bgc=${bgc}&locale=${locale}`;
// Update previous values
prevMonitor = monitor;
prevTheme = theme;
prevBgc = bgc;
prevLocale = locale;
dispatch("update", {
monitor: prevMonitor,
theme: prevTheme,
bgc: prevBgc,
locale: prevLocale
});
}
});
onDestroy(() => {
if (iframe) {
window.removeEventListener("message", listeners);
iframe.remove();
}
});
</script>
<!-- Dynamic container with unique ID -->
<div id={containerId}></div>
+356
View File
@@ -0,0 +1,356 @@
[
{ "id": "sdtd", "name": "7 Days to Die", "isValve": true },
{ "id": "arma2", "name": "ARMA 2", "isValve": true },
{ "id": "a2oa", "name": "ARMA 2: Operation Arrowhead", "isValve": true },
{ "id": "arma3", "name": "ARMA 3", "isValve": true },
{ "id": "aaa", "name": "ARMA: Armed Assault" },
{ "id": "acwa", "name": "ARMA: Cold War Assault" },
{ "id": "armareforger", "name": "ARMA: Reforger", "isValve": true },
{ "id": "armaresistance", "name": "ARMA: Resistance" },
{ "id": "abioticfactor", "name": "Abiotic Factor", "isValve": true },
{ "id": "aosc", "name": "Ace of Spades Classic" },
{ "id": "ahl", "name": "Action Half-Life", "isValve": true },
{ "id": "actionsource", "name": "Action: Source", "isValve": true },
{ "id": "aoc", "name": "Age of Chivalry", "isValve": true },
{ "id": "aoe2", "name": "Age of Empires 2" },
{ "id": "alienarena", "name": "Alien Arena" },
{ "id": "alienswarm", "name": "Alien Swarm", "isValve": true },
{ "id": "avp2", "name": "Aliens versus Predator 2" },
{ "id": "avp2010", "name": "Aliens vs. Predator 2010", "isValve": true },
{ "id": "americasarmy", "name": "America's Army" },
{ "id": "americasarmy2", "name": "America's Army 2" },
{ "id": "americasarmy3", "name": "America's Army 3", "isValve": true },
{ "id": "aapg", "name": "America's Army: Proving Grounds", "isValve": true },
{ "id": "asr08", "name": "Arca Sim Racing '08" },
{ "id": "asa", "name": "Ark: Survival Ascended" },
{ "id": "ase", "name": "Ark: Survival Evolved", "isValve": true },
{ "id": "armagetronadvanced", "name": "Armagetron Advanced" },
{ "id": "assettocorsa", "name": "Assetto Corsa" },
{ "id": "atlas", "name": "Atlas", "isValve": true },
{ "id": "avorion", "name": "Avorion", "isValve": true },
{ "id": "brokeprotocol", "name": "BROKE PROTOCOL" },
{ "id": "baldursgate", "name": "Baldur's Gate" },
{ "id": "ballisticoverkill", "name": "Ballistic Overkill", "isValve": true },
{ "id": "barotrauma", "name": "Barotrauma", "isValve": true },
{ "id": "basedefense", "name": "Base Defense", "isValve": true },
{ "id": "battalion1944", "name": "Battalion 1944", "isValve": true },
{ "id": "battlefield1942", "name": "Battlefield 1942" },
{ "id": "battlefield2", "name": "Battlefield 2" },
{ "id": "battlefield2142", "name": "Battlefield 2142" },
{ "id": "battlefield3", "name": "Battlefield 3" },
{ "id": "battlefield4", "name": "Battlefield 4" },
{ "id": "battlefieldhardline", "name": "Battlefield Hardline" },
{ "id": "battlefieldvietnam", "name": "Battlefield Vietnam" },
{ "id": "bbc2", "name": "Battlefield: Bad Company 2" },
{ "id": "beammp", "name": "BeamMP (2021)" },
{ "id": "blackmesa", "name": "Black Mesa", "isValve": true },
{ "id": "bladesymphony", "name": "Blade Symphony", "isValve": true },
{ "id": "brainbread", "name": "BrainBread", "isValve": true },
{ "id": "brainbread2", "name": "BrainBread 2", "isValve": true },
{ "id": "breach", "name": "Breach", "isValve": true },
{ "id": "breed", "name": "Breed" },
{ "id": "brink", "name": "Brink", "isValve": true },
{ "id": "bas", "name": "Build and Shoot" },
{ "id": "c2d", "name": "CS2D" },
{ "id": "cod", "name": "Call of Duty" },
{ "id": "cod2", "name": "Call of Duty 2" },
{ "id": "cod3", "name": "Call of Duty 3" },
{ "id": "cod4mw", "name": "Call of Duty 4: Modern Warfare" },
{ "id": "codbo3", "name": "Call of Duty: Black Ops 3", "isValve": true },
{ "id": "codmw2", "name": "Call of Duty: Modern Warfare 2" },
{ "id": "codmw3", "name": "Call of Duty: Modern Warfare 3", "isValve": true },
{ "id": "coduo", "name": "Call of Duty: United Offensive" },
{ "id": "codwaw", "name": "Call of Duty: World at War" },
{ "id": "coj", "name": "Call of Juarez" },
{ "id": "chaser", "name": "Chaser" },
{ "id": "cmw", "name": "Chivalry: Medieval Warfare", "isValve": true },
{ "id": "chrome", "name": "Chrome" },
{ "id": "codenamecure", "name": "Codename CURE", "isValve": true },
{ "id": "codenameeagle", "name": "Codename Eagle" },
{ "id": "colonysurvival", "name": "Colony Survival", "isValve": true },
{ "id": "cacr", "name": "Command and Conquer: Renegade" },
{ "id": "c3db", "name": "Commandos 3: Destination Berlin" },
{ "id": "conanexiles", "name": "Conan Exiles", "isValve": true },
{ "id": "contagion", "name": "Contagion", "isValve": true },
{ "id": "contractjack", "name": "Contract J.A.C.K." },
{ "id": "corekeeper", "name": "Core Keeper", "isValve": true },
{ "id": "counterstrike15", "name": "Counter-Strike 1.5" },
{ "id": "counterstrike16", "name": "Counter-Strike 1.6", "isValve": true },
{ "id": "counterstrike2", "name": "Counter-Strike 2", "isValve": true },
{ "id": "cscz", "name": "Counter-Strike: Condition Zero", "isValve": true },
{ "id": "csgo", "name": "Counter-Strike: Global Offensive", "isValve": true },
{ "id": "css", "name": "Counter-Strike: Source", "isValve": true },
{ "id": "creativerse", "name": "Creativerse", "isValve": true },
{ "id": "crce", "name": "Cross Racing Championship Extreme" },
{ "id": "crysis", "name": "Crysis" },
{ "id": "crysis2", "name": "Crysis 2" },
{ "id": "crysiswars", "name": "Crysis Wars" },
{ "id": "daikatana", "name": "Daikatana" },
{ "id": "dmomam", "name": "Dark Messiah of Might and Magic", "isValve": true },
{ "id": "dal", "name": "Dark and Light", "isValve": true },
{ "id": "dhe4445", "name": "Darkest Hour: Europe '44-'45" },
{ "id": "dod", "name": "Day of Defeat", "isValve": true },
{ "id": "dods", "name": "Day of Defeat: Source", "isValve": true },
{ "id": "dayofdragons", "name": "Day of Dragons", "isValve": true },
{ "id": "doi", "name": "Day of Infamy", "isValve": true },
{ "id": "dayz", "name": "DayZ", "isValve": true },
{ "id": "dayzmod", "name": "DayZ Mod", "isValve": true },
{ "id": "dow", "name": "Days of War", "isValve": true },
{ "id": "ddpt", "name": "Deadly Dozen: Pacific Theater" },
{ "id": "deathmatchclassic", "name": "Deathmatch Classic", "isValve": true },
{ "id": "deerhunter2005", "name": "Deer Hunter 2005" },
{ "id": "descent3", "name": "Descent 3" },
{ "id": "deusex", "name": "Deus Ex" },
{ "id": "devastation", "name": "Devastation" },
{ "id": "ddd", "name": "Dino D-Day", "isValve": true },
{ "id": "dtr2", "name": "Dirt Track Racing 2" },
{ "id": "discord", "name": "Discord" },
{ "id": "dst", "name": "Don't Starve Together", "isValve": true },
{ "id": "doom3", "name": "Doom 3" },
{ "id": "dota2", "name": "Dota 2", "isValve": true },
{ "id": "dab", "name": "Double Action: Boogaloo", "isValve": true },
{ "id": "dootf", "name": "Drakan: Order of the Flame" },
{ "id": "dnf2001", "name": "Duke Nukem Forever 2001" },
{ "id": "dystopia", "name": "Dystopia", "isValve": true },
{ "id": "eco", "name": "Eco" },
{ "id": "empiresmod", "name": "Empires Mod", "isValve": true },
{ "id": "egs", "name": "Empyrion - Galactic Survival", "isValve": true },
{ "id": "etqw", "name": "Enemy Territory: Quake Wars" },
{ "id": "ets2", "name": "Euro Truck Simulator 2", "isValve": true },
{ "id": "exfil", "name": "Exfil", "isValve": true },
{ "id": "fear", "name": "F.E.A.R." },
{ "id": "f1c9902", "name": "F1 Challenge '99-'02" },
{ "id": "foundry", "name": "FOUNDRY", "isValve": true },
{ "id": "factorio", "name": "Factorio" },
{ "id": "farcry", "name": "Far Cry" },
{ "id": "farcry2", "name": "Far Cry 2" },
{ "id": "farmingsimulator19", "name": "Farming Simulator 19" },
{ "id": "farmingsimulator22", "name": "Farming Simulator 22" },
{ "id": "farmingsimulator25", "name": "Farming Simulator 25" },
{ "id": "fof", "name": "Fistful of Frags", "isValve": true },
{ "id": "formulaone2002", "name": "Formula One 2002" },
{ "id": "fortressforever", "name": "Fortress Forever", "isValve": true },
{ "id": "ffow", "name": "Frontlines: Fuel of War" },
{ "id": "garrysmod", "name": "Garry's Mod", "isValve": true },
{ "id": "geneshift", "name": "Geneshift" },
{ "id": "gck", "name": "Giants: Citizen Kabuto" },
{ "id": "globaloperations", "name": "Global Operations" },
{ "id": "goldeneyesource", "name": "GoldenEye: Source", "isValve": true },
{ "id": "gus", "name": "Gore: Ultimate Soldier" },
{ "id": "gta5f", "name": "Grand Theft Auto V - FiveM" },
{ "id": "gta5r", "name": "Grand Theft Auto V - RageMP" },
{ "id": "gta5am", "name": "Grand Theft Auto V - alt:V Multiplayer" },
{ "id": "gtasamta", "name": "Grand Theft Auto: San Andreas - Multi Theft Auto" },
{ "id": "gtasam", "name": "Grand Theft Auto: San Andreas Multiplayer" },
{ "id": "gtasao", "name": "Grand Theft Auto: San Andreas OpenMP" },
{ "id": "gtavcmta", "name": "Grand Theft Auto: Vice City - Multi Theft Auto" },
{ "id": "groundbreach", "name": "Ground Breach", "isValve": true },
{ "id": "gunmanchronicles", "name": "Gunman Chronicles", "isValve": true },
{ "id": "hl2d", "name": "Half-Life 2: Deathmatch", "isValve": true },
{ "id": "hld", "name": "Half-Life Deathmatch", "isValve": true },
{ "id": "hlds", "name": "Half-Life Deathmatch: Source", "isValve": true },
{ "id": "hlof", "name": "Half-Life: Opposing Force", "isValve": true },
{ "id": "halo", "name": "Halo" },
{ "id": "halo2", "name": "Halo 2" },
{ "id": "eldewrito", "name": "Halo Online - ElDewrito" },
{ "id": "hawakening", "name": "Hawakening" },
{ "id": "hll", "name": "Hell Let Loose", "isValve": true },
{ "id": "heretic2", "name": "Heretic II" },
{ "id": "hexen2", "name": "Hexen II" },
{ "id": "hiddendangerous2", "name": "Hidden & Dangerous 2" },
{ "id": "homefront", "name": "Homefront", "isValve": true },
{ "id": "homeworld2", "name": "Homeworld 2" },
{ "id": "hurtworld", "name": "Hurtworld", "isValve": true },
{ "id": "i2cs", "name": "IGI 2: Covert Strike" },
{ "id": "i2s", "name": "IL-2 Sturmovik" },
{ "id": "icarus", "name": "Icarus", "isValve": true },
{ "id": "insurgency", "name": "Insurgency", "isValve": true },
{ "id": "imic", "name": "Insurgency: Modern Infantry Combat", "isValve": true },
{ "id": "insurgencysandstorm", "name": "Insurgency: Sandstorm", "isValve": true },
{ "id": "ironstorm", "name": "Iron Storm" },
{ "id": "jb0n", "name": "James Bond 007: Nightfire" },
{ "id": "jc2m", "name": "Just Cause 2 - Multiplayer" },
{ "id": "jc3m", "name": "Just Cause 3 - Multiplayer", "isValve": true },
{ "id": "kspd", "name": "Kerbal Space Program - DMP" },
{ "id": "killingfloor", "name": "Killing Floor" },
{ "id": "killingfloor2", "name": "Killing Floor 2", "isValve": true },
{ "id": "kloc", "name": "Kingpin: Life of Crime" },
{ "id": "kpctnc", "name": "Kiss: Psycho Circus: The Nightmare Child" },
{ "id": "kreedzclimbing", "name": "Kreedz Climbing", "isValve": true },
{ "id": "l4d", "name": "Left 4 Dead", "isValve": true },
{ "id": "l4d2", "name": "Left 4 Dead 2", "isValve": true },
{ "id": "m2m", "name": "Mafia II - Multiplayer" },
{ "id": "m2o", "name": "Mafia II - Online" },
{ "id": "moh", "name": "Medal of Honor" },
{ "id": "moha", "name": "Medal of Honor: Airborne" },
{ "id": "mohaa", "name": "Medal of Honor: Allied Assault" },
{ "id": "mohaab", "name": "Medal of Honor: Allied Assault Breakthrough" },
{ "id": "mohaas", "name": "Medal of Honor: Allied Assault Spearhead" },
{ "id": "mohpa", "name": "Medal of Honor: Pacific Assault" },
{ "id": "mohw", "name": "Medal of Honor: Warfighter" },
{ "id": "medievalengineers", "name": "Medieval Engineers", "isValve": true },
{ "id": "minecraft", "name": "Minecraft" },
{ "id": "mbe", "name": "Minecraft: Bedrock Edition" },
{ "id": "minetest", "name": "Minetest" },
{ "id": "mnc", "name": "Monday Night Combat", "isValve": true },
{ "id": "mordhau", "name": "Mordhau", "isValve": true },
{ "id": "mumble", "name": "Mumble" },
{ "id": "mgm", "name": "Mumble - GT Murmur" },
{ "id": "mutantfactions", "name": "Mutant Factions" },
{ "id": "moe", "name": "Myth of Empires", "isValve": true },
{ "id": "nascarthunder2004", "name": "NASCAR Thunder 2004" },
{ "id": "naturalselection", "name": "Natural Selection", "isValve": true },
{ "id": "naturalselection2", "name": "Natural Selection 2", "isValve": true },
{ "id": "nfshp2", "name": "Need for Speed: Hot Pursuit 2" },
{ "id": "nab", "name": "Nerf Arena Blast" },
{ "id": "neverwinternights", "name": "Neverwinter Nights" },
{ "id": "neverwinternights2", "name": "Neverwinter Nights 2" },
{ "id": "nexuiz", "name": "Nexuiz" },
{ "id": "nitrofamily", "name": "Nitro Family" },
{ "id": "nmrih", "name": "No More Room in Hell", "isValve": true },
{ "id": "nolf2asihw", "name": "No One Lives Forever 2: A Spy in H.A.R.M.'s Way" },
{ "id": "nla", "name": "Nova-Life: Amboise", "isValve": true },
{ "id": "nucleardawn", "name": "Nuclear Dawn", "isValve": true },
{ "id": "onset", "name": "Onset", "isValve": true },
{ "id": "openarena", "name": "OpenArena" },
{ "id": "openttd", "name": "OpenTTD" },
{ "id": "ofcwc", "name": "Operation Flashpoint: Cold War Crisis" },
{ "id": "ofr", "name": "Operation Flashpoint: Resistance" },
{ "id": "ohd", "name": "Operation: Harsh Doorstop", "isValve": true },
{ "id": "painkiller", "name": "Painkiller" },
{ "id": "palworld", "name": "Palworld" },
{ "id": "pvak2", "name": "Pirates, Vikings, and Knights II", "isValve": true },
{ "id": "pixark", "name": "PixARK", "isValve": true },
{ "id": "postscriptum", "name": "Post Scriptum", "isValve": true },
{ "id": "postal2", "name": "Postal 2" },
{ "id": "prey", "name": "Prey" },
{ "id": "pce", "name": "Primal Carnage: Extinction", "isValve": true },
{ "id": "projectcars", "name": "Project Cars", "isValve": true },
{ "id": "projectcars2", "name": "Project Cars 2", "isValve": true },
{ "id": "prb2", "name": "Project Reality: Battlefield 2" },
{ "id": "projectzomboid", "name": "Project Zomboid", "isValve": true },
{ "id": "quake", "name": "Quake" },
{ "id": "quake2", "name": "Quake 2" },
{ "id": "q3a", "name": "Quake 3: Arena" },
{ "id": "quake4", "name": "Quake 4" },
{ "id": "quakelive", "name": "Quake Live", "isValve": true },
{ "id": "rdkf", "name": "Rag Doll Kung Fu", "isValve": true },
{ "id": "rainbowsix", "name": "Rainbow Six" },
{ "id": "rs2rs", "name": "Rainbow Six 2: Rogue Spear" },
{ "id": "rs3rs", "name": "Rainbow Six 3: Raven Shield" },
{ "id": "rallisportchallenge", "name": "RalliSport Challenge" },
{ "id": "rallymasters", "name": "Rally Masters" },
{ "id": "rdr2r", "name": "Red Dead Redemption 2 - RedM" },
{ "id": "redorchestra", "name": "Red Orchestra" },
{ "id": "redorchestra2", "name": "Red Orchestra 2", "isValve": true },
{ "id": "roo4145", "name": "Red Orchestra: Ostfront 41-45" },
{ "id": "redline", "name": "Redline" },
{ "id": "renegade10", "name": "Renegade X" },
{ "id": "renown", "name": "Renown" },
{ "id": "rtcw", "name": "Return to Castle Wolfenstein" },
{ "id": "ricochet", "name": "Ricochet", "isValve": true },
{ "id": "ron", "name": "Rise of Nations" },
{ "id": "rs2v", "name": "Rising Storm 2: Vietnam", "isValve": true },
{ "id": "risingworld", "name": "Rising World", "isValve": true },
{ "id": "ror2", "name": "Risk of Rain 2", "isValve": true },
{ "id": "rune", "name": "Rune" },
{ "id": "rust", "name": "Rust", "isValve": true },
{ "id": "stalker", "name": "S.T.A.L.K.E.R." },
{ "id": "swat4", "name": "SWAT 4" },
{ "id": "satisfactory", "name": "Satisfactory" },
{ "id": "s2ats", "name": "Savage 2: A Tortured Soul" },
{ "id": "serioussam", "name": "Serious Sam" },
{ "id": "serioussam2", "name": "Serious Sam 2" },
{ "id": "sstse", "name": "Serious Sam: The Second Encounter" },
{ "id": "shatteredhorizon", "name": "Shattered Horizon", "isValve": true },
{ "id": "shogo", "name": "Shogo" },
{ "id": "shootmania", "name": "Shootmania" },
{ "id": "sin", "name": "SiN" },
{ "id": "sinepisodes", "name": "SiN Episodes", "isValve": true },
{ "id": "soldat", "name": "Soldat" },
{ "id": "sof", "name": "Soldier of Fortune" },
{ "id": "sof2", "name": "Soldier of Fortune 2" },
{ "id": "sotf", "name": "Sons Of The Forest", "isValve": true },
{ "id": "soulmask", "name": "Soulmask", "isValve": true },
{ "id": "spaceengineers", "name": "Space Engineers", "isValve": true },
{ "id": "squad", "name": "Squad", "isValve": true },
{ "id": "stbc", "name": "Star Trek: Bridge Commander" },
{ "id": "stvef", "name": "Star Trek: Voyager - Elite Force" },
{ "id": "stvef2", "name": "Star Trek: Voyager - Elite Force 2" },
{ "id": "swjk2jo", "name": "Star Wars Jedi Knight II: Jedi Outcast" },
{ "id": "swjkja", "name": "Star Wars Jedi Knight: Jedi Academy" },
{ "id": "swb", "name": "Star Wars: Battlefront" },
{ "id": "swb2", "name": "Star Wars: Battlefront 2" },
{ "id": "swrc", "name": "Star Wars: Republic Commando" },
{ "id": "starmade", "name": "StarMade" },
{ "id": "starbound", "name": "Starbound", "isValve": true },
{ "id": "starsiege", "name": "Starsiege" },
{ "id": "suicidesurvival", "name": "Suicide Survival", "isValve": true },
{ "id": "stn", "name": "Survive the Nights", "isValve": true },
{ "id": "svencoop", "name": "Sven Coop", "isValve": true },
{ "id": "synergy", "name": "Synergy", "isValve": true },
{ "id": "toxikk", "name": "TOXIKK" },
{ "id": "tacticalops", "name": "Tactical Ops" },
{ "id": "toh", "name": "Take On Helicopters" },
{ "id": "teamfactor", "name": "Team Factor" },
{ "id": "teamfortress2", "name": "Team Fortress 2", "isValve": true },
{ "id": "tfc", "name": "Team Fortress Classic", "isValve": true },
{ "id": "teamspeak2", "name": "Teamspeak 2" },
{ "id": "teamspeak3", "name": "Teamspeak 3" },
{ "id": "terminus", "name": "Terminus" },
{ "id": "terrariatshock", "name": "Terraria - TShock" },
{ "id": "theforest", "name": "The Forest", "isValve": true },
{ "id": "thefront", "name": "The Front", "isValve": true },
{ "id": "thehidden", "name": "The Hidden", "isValve": true },
{ "id": "theisle", "name": "The Isle", "isValve": true },
{ "id": "tie", "name": "The Isle Evrima" },
{ "id": "tonolf", "name": "The Operative: No One Lives Forever" },
{ "id": "theship", "name": "The Ship", "isValve": true },
{ "id": "thespecialists", "name": "The Specialists", "isValve": true },
{ "id": "tcgraw", "name": "Tom Clancy's Ghost Recon Advanced Warfighter" },
{ "id": "tcgraw2", "name": "Tom Clancy's Ghost Recon Advanced Warfighter 2" },
{ "id": "thps3", "name": "Tony Hawk's Pro Skater 3" },
{ "id": "thps4", "name": "Tony Hawk's Pro Skater 4" },
{ "id": "thu2", "name": "Tony Hawk's Underground 2" },
{ "id": "towerunite", "name": "Tower Unite", "isValve": true },
{ "id": "trackmania2", "name": "Trackmania 2" },
{ "id": "trackmaniaforever", "name": "Trackmania Forever" },
{ "id": "tremulous", "name": "Tremulous" },
{ "id": "t1s", "name": "Tribes 1: Starsiege" },
{ "id": "tribesvengeance", "name": "Tribes: Vengeance" },
{ "id": "tron20", "name": "Tron 2.0" },
{ "id": "turok2", "name": "Turok 2" },
{ "id": "universalcombat", "name": "Universal Combat" },
{ "id": "unreal", "name": "Unreal" },
{ "id": "u2tax", "name": "Unreal 2: The Awakening - XMP" },
{ "id": "unrealtournament", "name": "Unreal Tournament" },
{ "id": "unrealtournament2003", "name": "Unreal Tournament 2003" },
{ "id": "unrealtournament2004", "name": "Unreal Tournament 2004" },
{ "id": "unrealtournament3", "name": "Unreal Tournament 3" },
{ "id": "urbanterror", "name": "Urban Terror" },
{ "id": "vrising", "name": "V Rising", "isValve": true },
{ "id": "v8sc", "name": "V8 Supercar Challenge" },
{ "id": "valheim", "name": "Valheim", "isValve": true },
{ "id": "vampireslayer", "name": "Vampire Slayer", "isValve": true },
{ "id": "ventrilo", "name": "Ventrilo" },
{ "id": "vcm", "name": "Vice City Multiplayer" },
{ "id": "vietcong", "name": "Vietcong" },
{ "id": "vietcong2", "name": "Vietcong 2" },
{ "id": "vintagestory", "name": "Vintage Story" },
{ "id": "warfork", "name": "Warfork" },
{ "id": "warsow", "name": "Warsow" },
{ "id": "wot", "name": "Wheel of Time" },
{ "id": "wolfenstein", "name": "Wolfenstein" },
{ "id": "wet", "name": "Wolfenstein: Enemy Territory" },
{ "id": "wop", "name": "World Of Padman" },
{ "id": "wurmunlimited", "name": "Wurm Unlimited", "isValve": true },
{ "id": "xonotic", "name": "Xonotic" },
{ "id": "xpandrally", "name": "Xpand Rally" },
{ "id": "zombiemaster", "name": "Zombie Master", "isValve": true },
{ "id": "zps", "name": "Zombie Panic: Source", "isValve": true },
{ "id": "enshrouded", "name": "enshrouded", "isValve": true },
{ "id": "netpanzer", "name": "netPanzer" },
{ "id": "rfactor", "name": "rFactor" },
{ "id": "rfactor2", "name": "rFactor 2", "isValve": true },
{ "id": "unturned", "name": "unturned", "isValve": true }
]
-13
View File
@@ -1,13 +0,0 @@
// @ts-ignore
export const analyticsEvent = (event, data) => {
// Do something with the event and data
document.dispatchEvent(
new CustomEvent("analyticsEvent", {
bubbles: true,
detail: {
event,
data
}
})
);
};
+203
View File
@@ -0,0 +1,203 @@
export const DiscordJSONTemplate = JSON.stringify(
{
username: "{{site_name}}",
avatar_url: "{{{logo_url}}}",
content:
"## {{alert_name}}\n{{#is_triggered}}🔴 Triggered{{/is_triggered}}{{#is_resolved}}🟢 Resolved{{/is_resolved}}\n{{description}}\nClick [{{action_text}}]({{{action_url}}}) for more.",
embeds: [
{
title: "{{alert_name}}",
description: "{{description}}",
url: "{{{action_url}}}",
color: "{{#is_triggered}}13250616{{/is_triggered}}{{#is_resolved}}5156244{{/is_resolved}}",
fields: [
{
name: "Monitor",
value: "{{metric}}",
inline: false,
},
{
name: "Severity",
value: "{{severity}}",
inline: false,
},
{
name: "Alert ID",
value: "{{id}}",
inline: false,
},
{
name: "Current Value",
value: "{{current_value}}",
inline: true,
},
{
name: "Threshold",
value: "{{threshold}}",
inline: true,
},
],
footer: {
text: "{{source}}",
icon_url: "{{{logo_url}}}",
},
timestamp: "{{timestamp}}",
},
],
},
null,
2,
);
export const WebhookJSONTemplate = JSON.stringify(
{
id: "{{id}}",
alert_name: "{{alert_name}}",
severity: "{{severity}}",
status: "{{status}}",
source: "{{source}}",
timestamp: "{{timestamp}}",
description: "{{description}}",
details: {
metric: "{{metric}}",
current_value: "{{current_value}}",
threshold: "{{threshold}}",
},
actions: [
{
text: "{{action_text}}",
url: "{{{action_url}}}",
},
],
},
null,
2,
);
export const SlackJSONTemplate = JSON.stringify(
{
blocks: [
{
type: "header",
text: {
type: "plain_text",
text: "{{alert_name}}",
emoji: true,
},
},
{
type: "header",
text: {
type: "plain_text",
text: "{{#is_triggered}}🔴 Triggered{{/is_triggered}}{{#is_resolved}}🟢 Resolved{{/is_resolved}}",
emoji: true,
},
},
{
type: "section",
text: {
type: "mrkdwn",
text: "{{description}}\n*Source:* {{source}}\n*Severity:* {{severity}}\n*Status:* {{status}}",
},
},
{
type: "section",
fields: [
{
type: "mrkdwn",
text: "*Metric:*\n{{metric}}",
},
{
type: "mrkdwn",
text: "*Current Value:*\n{{current_value}}",
},
{
type: "mrkdwn",
text: "*Threshold:*\n{{threshold}}",
},
{
type: "mrkdwn",
text: "*Timestamp:*\n<!date^{{timestamp_unix}}^{date} at {time}|{{timestamp}}>",
},
],
},
{
type: "actions",
elements: [
{
type: "button",
text: {
type: "plain_text",
text: "{{action_text}}",
},
url: "{{{action_url}}}",
style: "primary",
},
],
},
],
},
null,
2,
);
export const DefaultAPIEval = `(async function (statusCode, responseTime, responseRaw, modules) {
let statusCodeShort = Math.floor(statusCode/100);
if(statusCode == 429 || (statusCodeShort >=2 && statusCodeShort <= 3)) {
return {
status: 'UP',
latency: responseTime,
}
}
return {
status: 'DOWN',
latency: responseTime,
}
})`;
export const DefaultPingEval = `(async function (arrayOfPings) {
let latencyTotal = arrayOfPings.reduce((acc, ping) => {
return acc + ping.latency;
}, 0);
let alive = arrayOfPings.reduce((acc, ping) => {
return acc && ping.alive;
}, true);
return {
status: alive ? 'UP' : 'DOWN',
latency: latencyTotal / arrayOfPings.length,
}
})`;
export const DefaultTCPEval = `(async function (arrayOfPings) {
let latencyTotal = arrayOfPings.reduce((acc, ping) => {
return acc + ping.latency;
}, 0);
let alive = arrayOfPings.reduce((acc, ping) => {
return acc && ping.status === "open";
}, true);
return {
status: alive ? 'UP' : 'DOWN',
latency: latencyTotal / arrayOfPings.length,
}
})`;
export const DefaultGamedigEval = `(async function (responseTime, responseRaw) {
return {
status: 'UP',
latency: responseTime,
}
})`;
export const GAMEDIG_TIMEOUT = 10 * 1000; // 10 seconds
export const GAMEDIG_SOCKET_TIMEOUT = 2 * 1000; // 2 seconds
export const ErrorSvg = `<svg xmlns="http://www.w3.org/2000/svg" width="120" height="60" viewBox="0 0 120 60" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="30" cy="24" r="10"/>
<path d="M26 27h8"/>
<path d="M26 21h2"/>
<path d="M32 21h2"/>
<text x="80" y="29" text-anchor="middle" font-family="system-ui, sans-serif" font-size="14" fill="currentColor" font-weight="300">Not Found</text>
</svg>`;
+17
View File
@@ -0,0 +1,17 @@
// @ts-ignore
export const analyticsEvent = (event, data) => {
// Do something with the event and data
if (typeof document !== "undefined") {
event = "kener_" + event;
document.dispatchEvent(
new CustomEvent("analyticsEvent", {
bubbles: true,
detail: {
event,
data,
},
}),
);
}
};
+306
View File
@@ -0,0 +1,306 @@
// @ts-nocheck
import { base } from "$app/paths";
function siteDataExtractFromDb(data, obj) {
let requestedObject = { ...obj };
for (const key in requestedObject) {
if (Object.prototype.hasOwnProperty.call(requestedObject, key)) {
const element = data[key];
if (data[key]) {
requestedObject[key] = data[key];
}
}
}
//remove any keys that are still null or empty
for (const key in requestedObject) {
if (Object.prototype.hasOwnProperty.call(requestedObject, key)) {
const element = requestedObject[key];
if (element === null || element === "") {
delete requestedObject[key];
}
}
}
return requestedObject;
}
//a function to make an api call to /manage/api/ to store site data
function storeSiteData(data) {
return fetch(base + "/manage/app/api/", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ action: "storeSiteData", data }),
});
}
const allRecordTypes = {
A: 1,
NS: 2,
MD: 3,
MF: 4,
CNAME: 5,
SOA: 6,
MB: 7,
MG: 8,
MR: 9,
NULL: 10,
WKS: 11,
PTR: 12,
HINFO: 13,
MINFO: 14,
MX: 15,
TXT: 16,
RP: 17,
AFSDB: 18,
X25: 19,
ISDN: 20,
RT: 21,
NSAP: 22,
NSAP_PTR: 23,
SIG: 24,
KEY: 25,
PX: 26,
GPOS: 27,
AAAA: 28,
LOC: 29,
NXT: 30,
EID: 31,
NIMLOC: 32,
SRV: 33,
ATMA: 34,
NAPTR: 35,
KX: 36,
CERT: 37,
A6: 38,
DNAME: 39,
SINK: 40,
OPT: 41,
APL: 42,
DS: 43,
SSHFP: 44,
IPSECKEY: 45,
RRSIG: 46,
NSEC: 47,
DNSKEY: 48,
DHCID: 49,
NSEC3: 50,
NSEC3PARAM: 51,
TLSA: 52,
SMIMEA: 53,
HIP: 55,
NINFO: 56,
RKEY: 57,
TALINK: 58,
CDS: 59,
CDNSKEY: 60,
OPENPGPKEY: 61,
CSYNC: 62,
SPF: 99,
UINFO: 100,
UID: 101,
GID: 102,
UNSPEC: 103,
NID: 104,
L32: 105,
L64: 106,
LP: 107,
EUI48: 108,
EUI64: 109,
TKEY: 249,
TSIG: 250,
IXFR: 251,
AXFR: 252,
MAILB: 253,
MAILA: 254,
ANY: 255,
};
const ValidateIpAddress = function (input) {
// Check if input is a valid IPv4 address with an optional port
const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/;
if (ipv4Regex.test(input)) {
return "IP4";
}
// Improved IPv6 regex that better handles compressed notation
const ipv6Regex =
/^(?:(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){0,7}:|(?:[0-9a-fA-F]{1,4}:){1,7}:|(?:[0-9a-fA-F]{1,4}:){6}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])|:(?:(?::[0-9a-fA-F]{1,4}){1,7}|:)|(?:[0-9a-fA-F]{1,4}:){1,7}(?::|:[0-9a-fA-F]{1,4})|(?:[0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,5}(?::[0-9a-fA-F]{1,4}){1,2}|(?:[0-9a-fA-F]{1,4}:){1,4}(?::[0-9a-fA-F]{1,4}){1,3}|(?:[0-9a-fA-F]{1,4}:){1,3}(?::[0-9a-fA-F]{1,4}){1,4}|(?:[0-9a-fA-F]{1,4}:){1,2}(?::[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:(?:(?::[0-9a-fA-F]{1,4}){1,6})|:(?:(?::[0-9a-fA-F]{1,4}){1,7}|:))$/;
if (ipv6Regex.test(input)) {
return "IP6";
}
// Check if input is a valid domain name with an optional port
const domainRegex = /^[a-zA-Z0-9]+([\-\.]{1}[a-zA-Z0-9]+)*\.[a-zA-Z]{2,}$/;
if (domainRegex.test(input)) {
return "DOMAIN";
}
// If none of the above conditions match, the input is invalid
return "Invalid";
};
function IsValidHost(domain) {
const regex = /^[a-zA-Z0-9]+([\-\.]{1}[a-zA-Z0-9]+)*\.[a-zA-Z]{2,}$/;
return regex.test(domain);
}
function IsValidNameServer(nameServer) {
//8.8.8.8 example
const regex = /^([0-9]{1,3}\.){3}[0-9]{1,3}$/;
return regex.test(nameServer);
}
const IsValidURL = function (url) {
return /^(http|https):\/\/[^ "]+$/.test(url);
};
function ValidateCronExpression(cronExp) {
// Check if expression is provided and is a string
if (!cronExp || typeof cronExp !== "string") {
return { isValid: false, message: "Cron expression must be a non-empty string" };
}
// Split the expression into its components
const fields = cronExp.trim().split(/\s+/);
// Standard cron should have 5 or 6 fields
// minute hour day-of-month month day-of-week [year]
if (fields.length < 5 || fields.length > 6) {
return {
isValid: false,
message: "Cron expression must have 5 or 6 fields",
};
}
// Define field constraints
const fieldConstraints = [
{ name: "minute", min: 0, max: 59 },
{ name: "hour", min: 0, max: 23 },
{ name: "day", min: 1, max: 31 },
{ name: "month", min: 1, max: 12 },
{ name: "weekday", min: 0, max: 6 },
{ name: "year", min: 1970, max: 2099 }, // Optional field
];
// Valid characters in cron expressions
const validChars = /^[\d/*,\-]+$/;
// Validate each field
for (let i = 0; i < fields.length; i++) {
const field = fields[i];
const constraint = fieldConstraints[i];
// Check for valid characters
if (!validChars.test(field)) {
return {
isValid: false,
message: `Invalid characters in ${constraint.name} field`,
};
}
// Handle special characters
if (field === "*") {
continue; // Asterisk is valid for all fields
}
// Handle lists (comma-separated values)
if (field.includes(",")) {
const values = field.split(",");
for (const value of values) {
if (!isValidRange(value, constraint.min, constraint.max)) {
return {
isValid: false,
message: `Invalid value in ${constraint.name} field: ${value}`,
};
}
}
continue;
}
// Handle ranges (with hyphens)
if (field.includes("-")) {
const [start, end] = field.split("-").map(Number);
if (start == null || end == null || start < constraint.min || end > constraint.max || start > end) {
return {
isValid: false,
message: `Invalid range in ${constraint.name} field: ${field}`,
};
}
continue;
}
// Handle steps (with forward slash)
if (field.includes("/")) {
const [range, step] = field.split("/");
if (range !== "*" && !isValidRange(range, constraint.min, constraint.max)) {
return {
isValid: false,
message: `Invalid range in ${constraint.name} field: ${range}`,
};
}
if (!isValidRange(step, 1, constraint.max)) {
return {
isValid: false,
message: `Invalid step value in ${constraint.name} field: ${step}`,
};
}
continue;
}
// Handle plain numbers
if (!isValidRange(field, constraint.min, constraint.max)) {
return {
isValid: false,
message: `Invalid value in ${constraint.name} field: ${field}`,
};
}
}
return { isValid: true, message: "Valid cron expression" };
}
function isValidRange(value, min, max) {
const num = Number(value);
return !isNaN(num) && num >= min && num <= max;
}
function IsValidPort(port) {
return isValidRange(port, 1, 65535);
}
function SortMonitor(monitorSort, resp) {
let monitors = [];
if (!!monitorSort && monitorSort.length > 0) {
monitors = monitorSort.map((id) => resp.find((m) => m.id == id)).filter((m) => !!m);
//append any new monitors
monitors = [...monitors, ...resp.filter((m) => !monitorSort.includes(m.id))];
} else {
monitors = resp;
}
return monitors;
}
//js function to generate 32 character random string
function RandomString(length) {
const chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
let result = "";
for (let i = length; i > 0; --i) result += chars[Math.floor(Math.random() * chars.length)];
return result;
}
/**
* Retreive game's data from its id.
* @param {string} id Id
* @return Returns game's data if found, undefined instead.
*/
function GetGameFromId(list, id) {
return list.find((game) => game.id === id);
}
export {
siteDataExtractFromDb,
storeSiteData,
allRecordTypes,
ValidateIpAddress,
IsValidHost,
IsValidNameServer,
IsValidURL,
IsValidPort,
ValidateCronExpression,
SortMonitor,
RandomString,
GetGameFromId,
};
+11 -9
View File
@@ -1,13 +1,15 @@
// @ts-nocheck
import { siteStore } from "./server/stores/site.js";
import { get } from "svelte/store";
const site = get(siteStore);
import { GetAllSiteData } from "$lib/server/controllers/controller.js";
const StatusColor = {
UP: site.colors?.UP || "#00dfa2",
DEGRADED: site.colors?.DEGRADED || "#e6ca61",
DOWN: site.colors?.DOWN || "#ca3038",
NO_DATA: "#f1f5f8"
};
async function StatusColor(status) {
let site = await GetAllSiteData();
return {
UP: site.colors?.UP || "#00dfa2",
DEGRADED: site.colors?.DEGRADED || "#e6ca61",
DOWN: site.colors?.DOWN || "#ca3038",
MAINTENANCE: site.colors?.MAINTENANCE || "#6679cc",
NO_DATA: "#f1f5f8"
};
}
export default StatusColor;
+269
View File
@@ -0,0 +1,269 @@
<script>
import { formatDistanceToNow, formatDistance } from "date-fns";
import Settings from "lucide-svelte/icons/settings";
import ArrowRight from "lucide-svelte/icons/arrow-right";
import Copy from "lucide-svelte/icons/copy";
import Check from "lucide-svelte/icons/check";
import { analyticsEvent } from "$lib/boringOne";
import * as Accordion from "$lib/components/ui/accordion";
import { l, f, fd, fdn } from "$lib/i18n/client";
import { base } from "$app/paths";
import { Button } from "$lib/components/ui/button";
import { Tooltip } from "bits-ui";
import GMI from "$lib/components/gmi.svelte";
import { page } from "$app/stores";
import { marked } from "marked";
import { onMount } from "svelte";
export let incident;
export let index;
export let allowCollapse = true;
let startTime = new Date(incident.start_date_time * 1000);
let endTime = new Date();
let nowTime = new Date();
if (incident.end_date_time) {
endTime = new Date(incident.end_date_time * 1000);
}
let incidentType = incident.incident_type;
const lastedFor = fd(startTime, endTime, $page.data.selectedLang);
const remainingTime = fd(nowTime, endTime, $page.data.selectedLang);
const startedAt = fdn(startTime, $page.data.selectedLang);
let incidentTimeStatus = "";
if (nowTime < startTime) {
incidentTimeStatus = "YET_TO_START";
} else if (nowTime >= startTime && nowTime <= endTime) {
incidentTimeStatus = "ONGOING";
} else if (nowTime > endTime) {
incidentTimeStatus = "COMPLETED";
}
let accordionValue = "incident-0";
if ($page.data.site.incidentGroupView == "COLLAPSED" && allowCollapse) {
accordionValue = "incident-collapse";
} else if ($page.data.site.incidentGroupView == "EXPANDED") {
accordionValue = index;
}
let incidentDateSummary = "";
let maintenanceBadge = "";
let maintenanceBadgeColor = "";
if (incidentTimeStatus == "YET_TO_START") {
incidentDateSummary = l($page.data.lang, "Starts %startedAt", { startedAt });
if (incidentType === "MAINTENANCE") {
incidentDateSummary = l($page.data.lang, "Starts %startedAt, will last for %lastedFor", {
startedAt,
lastedFor
});
maintenanceBadge = "Upcoming Maintenance";
maintenanceBadgeColor = "text-upcoming-maintenance";
}
} else if (incidentTimeStatus == "ONGOING") {
incidentDateSummary = l($page.data.lang, "Started %startedAt, still ongoing", {
startedAt
});
if (incidentType === "MAINTENANCE") {
incidentDateSummary = l($page.data.lang, "Started %startedAt, will last for %lastedFor more", {
startedAt,
lastedFor: remainingTime
});
maintenanceBadge = "Maintenance in Progress";
maintenanceBadgeColor = "text-maintenance-in-progress";
}
} else if (incidentTimeStatus == "COMPLETED") {
incidentDateSummary = l($page.data.lang, "Started %startedAt, lasted for %lastedFor", {
startedAt,
lastedFor
});
maintenanceBadge = "Maintenance Completed";
maintenanceBadgeColor = "text-maintenance-completed";
}
let pathMonitorLink = "";
onMount(async () => {
let protocol = window.location.protocol;
let domain = window.location.host;
pathMonitorLink = `${protocol}//${domain}${base}/view/events/${incident.incident_type}-${incident.id}`;
});
</script>
<div class="newincident relative grid w-full grid-cols-12 gap-2 px-0 py-0 last:border-b-0">
<div class="col-span-12">
<Accordion.Root
bind:value={index}
class="accor {allowCollapse === false ? 'hide-chevron' : ''}"
disabled={!allowCollapse}
>
<Accordion.Item value={accordionValue}>
<Accordion.Trigger
class="rounded-md px-4 hover:bg-muted hover:no-underline"
on:click={() => analyticsEvent("incident_open", { incident_title: incident.title })}
>
<div class="w-full text-left hover:no-underline">
<div class="flex justify-start gap-x-2">
<p class="flex gap-x-2 text-xs font-semibold">
{#if incidentType == "INCIDENT"}
<span class="badge-{incident.state}">
{l($page.data.lang, incident.state)}
</span>
{:else if incidentType == "MAINTENANCE"}
<span class="{maintenanceBadgeColor} ">
{l($page.data.lang, maintenanceBadge)}
</span>
{/if}
</p>
<div class="flex justify-end gap-x-2">
{#if $page.data.isLoggedIn}
<Button
href="{base}/manage/app/events#{incident.id}"
class="rotate-once h-5 p-0 text-muted-foreground hover:text-primary"
variant="link"
>
<Settings class="h-4 w-4 " />
</Button>
{/if}
{#if !!pathMonitorLink}
<Button
size="icon"
variant="link"
class="copybtn relative h-5 p-0 text-muted-foreground hover:text-primary"
on:click={(e) => {
e.stopPropagation();
navigator.clipboard.writeText(pathMonitorLink);
analyticsEvent("incident_copy_link", { incident_title: incident.title });
}}
>
<Check class="check-btn absolute left-0 top-0 h-4 w-4 text-green-500" />
<Copy class="copy-btn absolute left-0 top-0 h-4 w-4 " />
</Button>
{/if}
</div>
</div>
<p class="font-medium">
{incident.title}
</p>
{#if !!incidentDateSummary}
<Tooltip.Root openDelay={100} side="bottom" align="end">
<Tooltip.Trigger
class=" text-ellipsis whitespace-nowrap text-xs font-medium tracking-normal text-muted-foreground"
>
{incidentDateSummary}
</Tooltip.Trigger>
<Tooltip.Content class=" z-20 mt-11" side="bottom" align="end">
<div
class="items-center justify-center rounded border bg-primary px-1.5 py-1 text-xs font-medium text-primary-foreground shadow-popover"
>
{f(
new Date(incident.start_date_time * 1000),
"MMMM do yyyy, h:mm:ss a",
$page.data.selectedLang,
$page.data.localTz
)}
{#if incident.end_date_time}
<ArrowRight class="mx-1 -mt-0.5 inline h-3 w-3" />
{f(
new Date(incident.end_date_time * 1000),
"MMMM do yyyy, h:mm:ss a",
$page.data.selectedLang,
$page.data.localTz
)}
{:else}
<ArrowRight class="mx-1 -mt-0.5 inline h-3 w-3" />
<span class="dots-animation inline-block w-6 text-left"></span>
{/if}
</div>
</Tooltip.Content>
</Tooltip.Root>
{/if}
</div>
</Accordion.Trigger>
<Accordion.Content>
<div class="px-4 pt-2">
{#if incident.monitors.length > 0}
<div class="flex flex-wrap gap-2">
{#each incident.monitors as monitor}
<div class="tag-affected-text flex gap-x-2 rounded-md bg-secondary px-1 py-1 pr-2">
<div
class="bg-api-{monitor.impact_type.toLowerCase()} rounded px-1.5 py-1 text-xs font-semibold text-primary-foreground"
>
{monitor.impact_type}
</div>
{#if monitor.image}
<GMI src={monitor.image} classList="mt-1 h-4 w-4" />
{/if}
<div class="mt-0.5 font-medium">
{monitor.name}
</div>
</div>
{/each}
</div>
{/if}
<p class="my-3 text-xs font-semibold uppercase text-muted-foreground">
{l($page.data.lang, "Updates")}
</p>
{#if incident.comments.length > 0}
{#if incidentType == "INCIDENT"}
<ol class="relative mt-2 pl-14">
{#each incident.comments as comment}
<li class="relative border-l pb-4 pl-[4.5rem] last:border-0">
<div
class="absolute top-0 w-28 -translate-x-32 rounded border bg-secondary px-1.5 py-1 text-center text-xs font-semibold"
>
{l($page.data.lang, comment.state)}
</div>
<time class=" mb-1 text-sm font-medium leading-none text-muted-foreground">
{f(
new Date(comment.commented_at * 1000),
"MMMM do yyyy, h:mm:ss a",
$page.data.selectedLang,
$page.data.localTz
)}
</time>
<div class="mb-4 text-sm font-normal">
<div
class="kener-md prose prose-stone max-w-none dark:prose-invert prose-code:rounded prose-code:py-[0.2rem] prose-code:font-mono prose-code:text-sm prose-code:font-normal prose-pre:bg-opacity-0 dark:prose-pre:bg-neutral-900"
>
{@html marked.parse(comment.comment)}
</div>
</div>
</li>
{/each}
</ol>
{:else if incidentType == "MAINTENANCE"}
<ol class="relative mt-2 pl-0">
{#each incident.comments as comment}
<li class="relative pb-2 last:border-0">
<time class=" mb-1 text-sm font-medium leading-none text-muted-foreground">
{f(
new Date(comment.commented_at * 1000),
"MMMM do yyyy, h:mm:ss a",
$page.data.selectedLang,
$page.data.localTz
)}
</time>
<div class="mb-2 text-sm font-normal">
<div
class="kener-md prose prose-stone max-w-none dark:prose-invert prose-code:rounded prose-code:py-[0.2rem] prose-code:font-mono prose-code:text-sm prose-code:font-normal prose-pre:bg-opacity-0 dark:prose-pre:bg-neutral-900"
>
{@html marked.parse(comment.comment)}
</div>
</div>
</li>
{/each}
</ol>
{/if}
{:else}
<p class="text-sm font-medium">
{l($page.data.lang, "No Updates Yet")}
</p>
{/if}
</div>
</Accordion.Content>
</Accordion.Item>
</Accordion.Root>
</div>
</div>
+11
View File
@@ -0,0 +1,11 @@
<script>
import { base } from "$app/paths";
export let src = "";
export let alt = "GMI";
export let classList = "";
export let style = "";
export let srcset = "";
if (!src.startsWith("http")) src = `${base}${src}`;
</script>
<img {src} {alt} class={classList} {style} {srcset} />
-224
View File
@@ -1,224 +0,0 @@
<script>
import * as Card from "$lib/components/ui/card";
import { Separator } from "$lib/components/ui/separator";
import moment from "moment";
import { Button } from "$lib/components/ui/button";
import { Badge } from "$lib/components/ui/badge";
import { ChevronDown, Info, CalendarCheck, Hammer } from "lucide-svelte";
import * as Collapsible from "$lib/components/ui/collapsible";
import { l } from "$lib/i18n/client";
import axios from "axios";
import { Skeleton } from "$lib/components/ui/skeleton";
import { base } from "$app/paths";
import { tooltipAction } from "svelte-legos";
export let incident;
export let variant = "title+body+comments+monitor";
export let state = "open";
export let monitor;
export let lang;
const StatusObj = {
UP: "api-up",
DEGRADED: "api-degraded",
DOWN: "api-down",
NO_DATA: "api-nodata"
};
let blinker = "bg-transparent";
let incidentPriority = "";
let incidentDuration = 0;
if (incident.labels.includes("incident-down")) {
blinker = "bg-red-500";
incidentPriority = "DOWN";
} else if (incident.labels.includes("incident-degraded")) {
blinker = "bg-yellow-500";
incidentPriority = "DEGRADED";
}
let incidentState = incident.state;
let incidentClosedAt = incident.incident_end_time;
let incidentCreatedAt = incident.incident_start_time;
let incidentMessage = "";
if (!!incidentClosedAt && !!incidentCreatedAt) {
//incidentDuration between closed_at and created_at
incidentDuration = moment(incidentClosedAt * 1000)
.add(1, "minutes")
.diff(moment(incidentCreatedAt * 1000), "minutes");
} else if (!!incidentCreatedAt) {
//incidentDuration between now and created_at
incidentDuration = moment().diff(moment(incidentCreatedAt * 1000), "minutes");
}
//find a replace /\[start_datetime:(\d+)\]/ empty in incident.body
//find a replace /\[end_datetime:(\d+)\]/ empty in incident.body
incident.body = incident.body.replace(/\[start_datetime:(\d+)\]/g, "");
incident.body = incident.body.replace(/\[end_datetime:(\d+)\]/g, "");
//fetch comments
incident.comments = [];
let commentsLoading = true;
function getComments() {
if (incident.comments.length > 0) return;
if (commentsLoading === false) return;
axios
.get(`${base}/incident/${incident.number}/comments`)
.then((response) => {
incident.comments = response.data;
commentsLoading = false;
})
.catch((error) => {
// console.log(error);
});
}
$: {
if (state == "open") {
getComments();
}
}
</script>
<div class="incident-div relative mb-8 grid w-full grid-cols-3 gap-4">
<div class="col-span-3">
<Card.Root>
<Card.Header class="pb-2 pt-3">
<Card.Title class="relative mb-0">
{#if incidentPriority != "" && incidentDuration > 0}
<p class="absolute -top-8 -translate-y-1 leading-10">
<Badge
class="-ml-4 bg-card text-xs font-semibold text-[rgba(0,0,0,.6)] text-{StatusObj[
incidentPriority
]} "
>
{incidentPriority} for {incidentDuration} Minute{incidentDuration >
1
? "s"
: ""}
</Badge>
</p>
{/if}
{#if monitor.image}
<img
src={monitor.image.startsWith("/")
? base + monitor.image
: monitor.image}
class="absolute left-0 top-1 inline h-5 w-5"
alt=""
srcset=""
/>
{/if}
<div class="px-8">
<div class="scroll-m-20 text-xl font-medium tracking-tight">
{#if variant.includes("monitor")}
{monitor.name} -
{/if}
{#if variant.includes("title")}
{incident.title}
{/if}
</div>
</div>
{#if incidentState == "open"}
<span
class="absolute -left-[26px] -top-[16px] inline-flex h-[8px] w-[8px] animate-ping rounded-full {blinker} opacity-75"
></span>
{/if}
{#if variant.includes("body") || variant.includes("comments")}
<div class="toggle absolute right-4 {state}">
<Button
variant="outline"
class="h-6 w-6 rounded-full"
size="icon"
on:click={() => {
state = state == "open" ? "close" : "open";
}}
>
<ChevronDown class="text-muted-foreground" size={12} />
</Button>
</div>
{/if}
</Card.Title>
<Card.Description class="-mt-4 px-8 text-xs ">
{moment(incidentCreatedAt * 1000).format("MMMM Do YYYY, h:mm:ss a")}
<p class="mt-0 flex gap-2 leading-8">
{#if incident.labels.includes("identified")}
<span
class="tag-identified-text"
title={l(lang, "incident.identified")}
>
<Info class="inline h-4 w-4" />
</span>
{/if}
{#if incident.labels.includes("resolved")}
<span class="tag-resolved-text" title={l(lang, "incident.resolved")}>
<CalendarCheck class="inline h-4 w-4" />
</span>
{/if}
{#if incident.labels.includes("maintenance")}
<span
class="tag-maintenance-text"
title={l(lang, "incident.maintenance")}
>
<Hammer class="inline h-4 w-4" />
</span>
{/if}
</p>
</Card.Description>
</Card.Header>
{#if (variant.includes("body") || variant.includes("comments")) && state == "open"}
<Card.Content class="px-14">
{#if variant.includes("body")}
<div
class="prose prose-stone max-w-none text-sm dark:prose-invert prose-code:rounded prose-code:px-[0.3rem] prose-code:py-[0.2rem] prose-code:font-mono prose-code:text-sm"
>
{@html incident.body}
</div>
{/if}
{#if variant.includes("comments") && incident.comments?.length > 0}
<div class="ml-4 mt-8 px-4">
<ol class="relative border-s border-secondary">
{#each incident.comments as comment}
<li class="mb-10 ms-4">
<div
class="absolute -start-1.5 mt-1.5 h-3 w-3 rounded-full border border-secondary bg-secondary"
></div>
<time
class="mb-1 text-xs font-normal leading-none text-muted-foreground"
>
{moment(comment.created_at).format(
"MMMM Do YYYY, h:mm:ss a"
)}
</time>
<div
class="wysiwyg prose prose-stone mb-4 max-w-none text-base text-sm font-normal dark:prose-invert prose-code:rounded prose-code:px-[0.3rem] prose-code:py-[0.2rem] prose-code:font-mono prose-code:text-sm"
>
{@html comment.body}
</div>
</li>
{/each}
</ol>
</div>
{:else if commentsLoading}
<Skeleton class="h-[20px] w-[100px] rounded-full" />
{/if}
</Card.Content>
{/if}
</Card.Root>
</div>
</div>
<style>
.toggle {
display: none;
}
.toggle {
transition: all 0.15s ease-in-out;
}
.toggle.open {
transform: rotate(180deg);
}
.incident-div:hover .toggle {
display: block;
}
</style>
+205
View File
@@ -0,0 +1,205 @@
<script>
import { onMount } from "svelte";
import { base } from "$app/paths";
import moment from "moment";
import Loader from "lucide-svelte/icons/loader";
import ChevronLeft from "lucide-svelte/icons/chevron-left";
import ChevronRight from "lucide-svelte/icons/chevron-right";
export let data;
let pageNo = 1;
let limit = 20;
let alerts = [];
let totalPages = 0;
let loadingData = false;
let incidentURL = "";
if (data.siteData.github && data.siteData.github.owner && data.siteData.github.repo) {
incidentURL = `https://github.com/${data.siteData.github.owner}/${data.siteData.github.repo}/issues`;
}
async function loadData() {
loadingData = true;
try {
let apiResp = await fetch(base + "/manage/app/api/", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
action: "getAllAlertsPaginated",
data: { page: pageNo, limit }
})
});
let resp = await apiResp.json();
alerts = resp.alerts;
totalPages = Math.ceil(resp.total.count / limit);
} catch (error) {
alert("Error: " + error);
} finally {
loadingData = false;
}
}
onMount(async () => {
await loadData();
});
</script>
<div class="mt-4">
<div class="mb-4 flex justify-between">
<div>
{#if loadingData}
<Loader class="mt-6 h-4 w-4 animate-spin" />
{/if}
</div>
<!-- Pagination -->
{#if totalPages > 1}
<nav class="flex max-w-2xl items-center gap-x-1" aria-label="Pagination">
<button
disabled={pageNo === 1}
type="button"
class="inline-flex min-h-[38px] min-w-[38px] items-center justify-center gap-x-2 rounded-lg border border-transparent px-2.5 py-2 text-sm text-gray-800 hover:bg-gray-100 focus:bg-gray-100 focus:outline-none disabled:pointer-events-none disabled:opacity-50 dark:border-transparent dark:text-white dark:hover:bg-white/10 dark:focus:bg-white/10"
aria-label="Previous"
on:click={() => {
pageNo--;
loadData();
}}
>
<ChevronLeft class="size-3.5 shrink-0" />
<span class="sr-only">Previous</span>
</button>
<div class="flex max-w-[380px] items-center gap-x-1 overflow-x-auto">
{#each Array.from({ length: totalPages }, (_, i) => i + 1) as page}
<button
on:click={() => {
pageNo = page;
loadData();
}}
type="button"
class="flex min-h-[38px] min-w-[38px] items-center justify-center rounded-lg border {page == pageNo
? 'border-gray-200'
: 'border-transparent'} px-3 py-2 text-sm text-gray-800 hover:bg-gray-100 focus:bg-gray-100 focus:outline-none disabled:pointer-events-none disabled:opacity-50 dark:border-transparent dark:text-white dark:hover:bg-white/10 dark:focus:bg-white/10"
>
{page}
</button>
{/each}
</div>
<button
type="button"
disabled={pageNo === totalPages}
class="inline-flex min-h-[38px] min-w-[38px] items-center justify-center gap-x-2 rounded-lg border border-transparent px-2.5 py-2 text-sm text-gray-800 hover:bg-gray-100 focus:bg-gray-100 focus:outline-none disabled:pointer-events-none disabled:opacity-50 dark:border-transparent dark:text-white dark:hover:bg-white/10 dark:focus:bg-white/10"
aria-label="Next"
on:click={() => {
pageNo++;
loadData();
}}
>
<span class="sr-only">Next</span>
<ChevronRight class="size-3.5 shrink-0" />
</button>
</nav>
{/if}
<!-- End Pagination -->
</div>
<div class="flex flex-col">
<div class="-m-1.5 overflow-x-auto">
<div class="inline-block min-w-full p-1.5 align-middle">
<div class="overflow-hidden rounded-lg border dark:border-neutral-700">
<table class="min-w-full divide-y divide-gray-200 dark:divide-neutral-700">
<thead>
<tr>
<th
scope="col"
class="px-6 py-3 text-start text-xs font-medium uppercase text-gray-500 dark:text-neutral-500">ID</th
>
<th
scope="col"
class="px-6 py-3 text-start text-xs font-medium uppercase text-gray-500 dark:text-neutral-500"
>Type</th
>
<th
scope="col"
class="px-6 py-3 text-start text-xs font-medium uppercase text-gray-500 dark:text-neutral-500"
>Monitor</th
>
<th
scope="col"
class="px-6 py-3 text-start text-xs font-medium uppercase text-gray-500 dark:text-neutral-500"
>Status</th
>
<th
scope="col"
class="px-6 py-3 text-start text-xs font-medium uppercase text-gray-500 dark:text-neutral-500"
>Checks</th
>
<th
scope="col"
class="px-6 py-3 text-start text-xs font-medium uppercase text-gray-500 dark:text-neutral-500"
>Incident</th
>
<th
scope="col"
class="px-6 py-3 text-start text-xs font-medium uppercase text-gray-500 dark:text-neutral-500"
>Created At</th
>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 dark:divide-neutral-700">
{#each alerts as alert}
<tr>
<td class="whitespace-nowrap px-6 py-4 text-sm font-medium">
{alert.id}
</td>
<td class="whitespace-nowrap px-6 py-4 text-xs font-semibold">
{#if alert.monitor_status === "DOWN"}
<span class="text-red-500">
{alert.monitor_status}
</span>
{:else}
<span class="text-yellow-500">
{alert.monitor_status}
</span>
{/if}
</td>
<td class="whitespace-nowrap px-6 py-4 text-sm">
{alert.monitor_tag}
</td>
<td class="whitespace-nowrap px-6 py-4 text-xs font-semibold">
{#if alert.alert_status === "RESOLVED"}
<span class="text-blue-500">
{alert.alert_status}
</span>
{:else}
<span class="text-fuchsia-500">
{alert.alert_status}
</span>
{/if}
</td>
<td class="whitespace-nowrap px-6 py-4 text-sm">
{alert.health_checks}
</td>
<td class="whitespace-nowrap px-6 py-4 text-xs font-semibold">
{#if !!alert.incident_number}
<a
rel="external"
href="{base}/manage/app/events#{alert.incident_number}"
class="text-cyan-500 hover:underline focus:outline-none"
>
{alert.incident_number}
</a>
{:else}
-
{/if}
</td>
<td class="whitespace-nowrap px-6 py-4 text-sm">
{moment.utc(alert.created_at, "YYYY-MM-DD HH:mm:ss").local().format("YYYY-MM-DD HH:mm:ss")}
</td>
</tr>
{/each}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>

Some files were not shown because too many files have changed in this diff Show More