Compare commits

..

20 Commits

Author SHA1 Message Date
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
10 changed files with 149 additions and 152 deletions
+8 -42
View File
@@ -1,4 +1,4 @@
name: Publish Docker Images to Container Registries
name: Publish Docker Image to Registries
on:
release:
@@ -7,6 +7,8 @@ on:
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
@@ -37,31 +39,9 @@ jobs:
exit 1
)
check-dependabot-prs:
runs-on: ubuntu-latest
outputs:
has_dependabot_prs: ${{ steps.check.outputs.has_dependabot_prs }}
steps:
- name: Check for Open Dependabot PRs
id: check
run: |
PRS=$(gh pr list --repo ${{ github.repository }} --author "dependabot[bot]" --state open --json number --jq 'length')
echo "Open Dependabot PRs: $PRS"
if [ "$PRS" -gt 0 ]; then
echo "has_dependabot_prs=true" >> $GITHUB_ENV
exit 1 # Fail the workflow
else
echo "has_dependabot_prs=false" >> $GITHUB_ENV
fi
env:
GH_TOKEN: ${{ secrets.GH_PAT }}
build-and-push-to-registries:
# Runs only after `check-lockfile` and `check-dependabot-prs` jobs complete successfully
needs: [check-lockfile, check-dependabot-prs]
needs: check-lockfile # Runs only after `check-lockfile` completes successfully
name: Push Docker images to Docker Hub and GitHub Container Registry
# Ensures that there are no open Dependabot PRs before building Docker images
if: needs.check-dependabot-prs.outputs.has_dependabot_prs == 'false'
strategy:
matrix:
variant: [alpine, debian]
@@ -105,7 +85,7 @@ jobs:
with:
registry: ${{ env.GITHUB_REGISTRY }}
username: ${{ github.repository_owner }}
password: ${{ secrets.GH_PAT }}
password: ${{ secrets.GITHUB_TOKEN }}
# Combined metadata extraction for both registries
- name: Extract Docker metadata
@@ -131,22 +111,8 @@ jobs:
- name: Set up QEMU
uses: docker/setup-qemu-action@v3.3.0
- name: Load environment variables from .env.build
run: |
# If .env.build is missing, use default full image names (should match Dockerfile ARG defaults)
export ALPINE_VERSION="node:23.7.0-alpine3.21"
export DEBIAN_VERSION="node:23.7.0-bookworm-slim"
# If .env.build exists, override fallback values with Dependabot-updated values
if [ -f .env.build ]; then
export $(grep -v '^#' .env.build | xargs)
fi
echo "ALPINE_VERSION=$ALPINE_VERSION" >> $GITHUB_ENV
echo "DEBIAN_VERSION=$DEBIAN_VERSION" >> $GITHUB_ENV
# Build and push Docker image with Buildx to both registries (don't push on PR)
- name: Build and push Docker images
- name: Build and push Docker image
id: build-and-push
uses: docker/build-push-action@v6.13.0
with:
@@ -172,7 +138,7 @@ jobs:
echo "${TAGS}" | xargs -I {} cosign sign --yes {}@${DIGEST}
# For use in other workflows (e.g. 'generate-readme', etc.)
- name: Save release's build version number to repository variable
- name: Save Build Version to Repository Variable
if: matrix.variant == 'debian' && github.run_attempt == 1
run: |
# VERSION="${{ steps.meta.outputs.version }}"
@@ -187,4 +153,4 @@ jobs:
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
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
+8 -8
View File
@@ -1,15 +1,15 @@
# syntax=docker/dockerfile:1
# Global build arguments (defined default values in case `.env.build` isn't loaded)
ARG ALPINE_VERSION=node:23.7.0-alpine3.21
ARG DEBIAN_VERSION=node:23.7.0-bookworm-slim
ARG ALPINE_VERSION_TAG=23.7.0-alpine3.21
ARG DEBIAN_VERSION_TAG=23.7.0-bookworm-slim
ARG VARIANT=debian
#==========================================================#
# STAGE 1: BUILD STAGE #
#==========================================================#
FROM ${DEBIAN_VERSION} AS builder-debian
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 \
@@ -22,7 +22,7 @@ RUN apt-get update && apt-get install -y \
iputils-ping=3:20221126-1+deb12u1 && \
rm -rf /var/lib/apt/lists/*
FROM ${ALPINE_VERSION} AS builder-alpine
FROM node:${ALPINE_VERSION_TAG} AS builder-alpine
RUN apk add --no-cache --update \
build-base=0.5-r3 \
python3=3.12.9-r0 \
@@ -31,7 +31,7 @@ RUN apk add --no-cache --update \
g++=14.2.0-r4 \
sqlite=3.48.0-r0 \
sqlite-dev=3.48.0-r0 \
tzdata=2025a-r0 \
tzdata \
iputils=20240905-r0
FROM builder-${VARIANT} AS builder
@@ -71,7 +71,7 @@ RUN npm run build && \
# STAGE 2: PRODUCTION/FINAL STAGE #
#==========================================================#
FROM ${DEBIAN_VERSION} AS final-debian
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 \
@@ -81,11 +81,11 @@ RUN apt-get update && apt-get install -y \
curl && \
rm -rf /var/lib/apt/lists/*
FROM ${ALPINE_VERSION} AS final-alpine
FROM node:${ALPINE_VERSION_TAG} AS final-alpine
RUN apk add --no-cache --update \
iputils=20240905-r0 \
sqlite=3.48.0-r0 \
tzdata=2025a-r0
tzdata
FROM final-${VARIANT} AS final
+9 -9
View File
@@ -79,7 +79,7 @@ npm run dev
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.4"><img src="https://img.shields.io/badge/Latest_Stable_Release-3.2.4-blue" alt="Kener latest stable version: 3.2.4" /></a>
<a href="https://hub.docker.com/r/rajnandan1/kener/tags?page=1&ordering=last_updated&name=3.2.9"><img src="https://img.shields.io/badge/Latest_Stable_Release-3.2.9-blue" alt="Kener latest stable version: 3.2.9" /></a>
#### Available Tags
@@ -93,38 +93,38 @@ Official Docker images for **Kener** are available on [Docker Hub](https://hub.d
</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.4)</td>
<td>Latest stable release (aka 3.2.9)</td>
</tr>
<tr>
<td><a href="https://hub.docker.com/r/rajnandan1/kener/tags?page=1&ordering=last_updated&name=3.2.4" target="_blank"><code>3.2.4</code></a></td>
<td><a href="https://hub.docker.com/r/rajnandan1/kener/tags?page=1&ordering=last_updated&name=3.2.9" target="_blank"><code>3.2.9</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.4) release within that minor version (3.2.x)</td>
<td>Major-minor version tag pointing to the latest patch (3.2.9) 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.4) release within that major version (3.x.x)</td>
<td>Major version tag pointing to the latest stable (3.2.9) 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.4)</td>
<td>Latest stable release (aka 3.2.9)</td>
</tr>
<tr>
<td><a href="https://hub.docker.com/r/rajnandan1/kener/tags?page=1&ordering=last_updated&name=3.2.4-alpine" target="_blank"><code>3.2.4-alpine</code></a></td>
<td><a href="https://hub.docker.com/r/rajnandan1/kener/tags?page=1&ordering=last_updated&name=3.2.9-alpine" target="_blank"><code>3.2.9-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.4) release within that minor version (3.2.x)</td>
<td>Major-minor version tag pointing to the latest patch (3.2.9) 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.4) release within that major version (3.x.x)</td>
<td>Major version tag pointing to the latest stable (3.2.9) release within that major version (3.x.x)</td>
</tr>
</table>
+1 -1
View File
@@ -119,7 +119,7 @@ Admins can manage user accounts through the user settings page:
## Role Limitations
- The Admin role can only be assigned during initial setup or by existing Admins
- 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)
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "kener",
"version": "3.2.7",
"version": "3.2.10",
"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.",
+13 -1
View File
@@ -188,6 +188,12 @@
invalidFormMessage = `Host ${hosts[i].host} is not of type ${hosts[i].type}`;
return;
}
//validating timeout for ping
if (!!hosts[i].timeout && isNaN(hosts[i].timeout)) {
invalidFormMessage = "Timeout should be a number";
return;
}
hosts[i].timeout = Number(hosts[i].timeout);
//validating timeout
if (hosts[i].timeout < 1) {
invalidFormMessage = "Timeout should be greater than 0";
@@ -222,6 +228,12 @@
invalidFormMessage = `Host ${hosts[i].host} is not of type ${hosts[i].type}`;
return;
}
//validating timeout for tcp
if (!!hosts[i].timeout && isNaN(hosts[i].timeout)) {
invalidFormMessage = "Timeout should be a number";
return;
}
hosts[i].timeout = Number(hosts[i].timeout);
//validating timeout
if (hosts[i].timeout < 1) {
invalidFormMessage = "Timeout should be greater than 0";
@@ -943,7 +955,7 @@
];
}}
>
<Plus class="h-5 w-5" /> Add New
<Plus class="mr-1 h-5 w-5" /> Add New
</Button>
</div>
</div>
+29 -23
View File
@@ -229,28 +229,33 @@
});
let shareMenusToggle = false;
let monitorTriggers = {
down_trigger: {
failureThreshold: 1,
trigger_type: "DOWN",
successThreshold: 1,
description: "The monitor is down",
createIncident: "NO",
active: false,
triggers: [],
severity: "critical"
},
degraded_trigger: {
failureThreshold: 1,
trigger_type: "DEGRADED",
successThreshold: 1,
active: false,
description: "The monitor is degraded",
createIncident: "NO",
triggers: [],
severity: "warning"
}
};
function initMonitorTriggers() {
monitorTriggers = {
down_trigger: {
failureThreshold: 1,
trigger_type: "DOWN",
successThreshold: 1,
description: "The monitor is down",
createIncident: "NO",
active: false,
triggers: [],
severity: "critical"
},
degraded_trigger: {
failureThreshold: 1,
trigger_type: "DEGRADED",
successThreshold: 1,
active: false,
description: "The monitor is degraded",
createIncident: "NO",
triggers: [],
severity: "warning"
}
};
}
let monitorTriggers;
initMonitorTriggers();
let saveTriggerError = "";
async function saveTriggers() {
@@ -286,6 +291,7 @@
}
let currentAlertMonitor;
function openAlertMenu(m) {
initMonitorTriggers();
currentAlertMonitor = m;
if (m.down_trigger) {
monitorTriggers.down_trigger = m.down_trigger;
@@ -503,7 +509,7 @@
<div class="text-red-500">
{monitor.testResult.error}
</div>
{:else if !!monitor.testResult.status && !!monitor.testResult.latency}
{:else if monitor.testResult.status != undefined && monitor.testResult.latency != undefined}
<p class="text-muted-foreground">
Status: <span class="text-api-{monitor.testResult.status.toLowerCase()}"
>{monitor.testResult.status}</span
+11 -4
View File
@@ -138,9 +138,9 @@ class DbImpl {
return data;
}
async getLastStatusBeforeCombined(monitor_tags_arr, timestamp) {
async getLastStatusBeforeCombined(monitor_tags_arr, timestamp, minTimestamp) {
//monitor_tag_arr is an array of string
return await this.knex("monitoring_data")
let query = this.knex("monitoring_data")
.select(
"timestamp",
this.knex.raw("COUNT(*) as total_entries"),
@@ -153,8 +153,15 @@ class DbImpl {
END as status
`),
)
.whereIn("monitor_tag", monitor_tags_arr)
.where("timestamp", "=", timestamp)
.whereIn("monitor_tag", monitor_tags_arr);
if (!!minTimestamp) {
query = query.whereBetween("timestamp", [minTimestamp, timestamp]);
} else {
query = query.where("timestamp", "=", timestamp);
}
return await query
.groupBy("timestamp")
.havingRaw("COUNT(*) = ?", [monitor_tags_arr.length])
.orderBy("timestamp", "desc")
+64 -63
View File
@@ -3,85 +3,86 @@ import net from "net"; // Use import instead of require
import ping from "ping";
const TCP = function (type, host, port, timeout) {
port = parseInt(port, 10);
return new Promise((resolve) => {
const socket = new net.Socket();
const start = process.hrtime.bigint(); // High-precision timestamp
let resolved = false;
timeout = Number(timeout);
port = parseInt(port, 10);
return new Promise((resolve) => {
const socket = new net.Socket();
const start = process.hrtime.bigint(); // High-precision timestamp
let resolved = false;
const onFinish = (status) => {
if (!resolved) {
resolved = true;
const end = process.hrtime.bigint();
const latency = Number(end - start) / 1e6; // Convert nanoseconds to milliseconds
socket.destroy();
resolve({ status, latency, host, port, type });
}
};
const onFinish = (status) => {
if (!resolved) {
resolved = true;
const end = process.hrtime.bigint();
const latency = Number(end - start) / 1e6; // Convert nanoseconds to milliseconds
socket.destroy();
resolve({ status, latency, host, port, type });
}
};
socket.setTimeout(timeout);
socket.once("connect", () => onFinish("open"));
socket.once("timeout", () => onFinish("timeout"));
socket.once("error", () => onFinish("error"));
socket.setTimeout(timeout);
socket.once("connect", () => onFinish("open"));
socket.once("timeout", () => onFinish("timeout"));
socket.once("error", () => onFinish("error"));
// Check if it's an IPv6 address (contains ':')
const options = {
host,
port,
family: type === "IP6" ? 6 : 4
}; //host.includes(":") ? { host, port, family: 6 } : { host, port };
socket.connect(options);
});
// Check if it's an IPv6 address (contains ':')
const options = {
host,
port,
family: type === "IP6" ? 6 : 4,
}; //host.includes(":") ? { host, port, family: 6 } : { host, port };
socket.connect(options);
});
};
const Ping = async function (type, host, timeout, count) {
let output = {
alive: false,
min: null,
max: null,
avg: null,
latencies: [],
latency: null,
host: host,
type: type
};
let output = {
alive: false,
min: null,
max: null,
avg: null,
latencies: [],
latency: null,
host: host,
type: type,
};
try {
let res = await ping.promise.probe(host, {
v6: type === "IP6",
timeout: type === "IP6" ? false : Math.floor(timeout / 1000),
min_reply: count
});
output.alive = res.alive;
output.min = res.min;
output.max = res.max;
output.avg = res.avg;
output.latencies = res.times;
output.latency = res.time;
} catch (error) {
console.log(`Error in pingCall IP4 for ${host}`, error);
}
return output;
try {
let res = await ping.promise.probe(host, {
v6: type === "IP6",
timeout: type === "IP6" ? false : Math.floor(timeout / 1000),
min_reply: count,
});
output.alive = res.alive;
output.min = res.min;
output.max = res.max;
output.avg = res.avg;
output.latencies = res.times;
output.latency = res.time;
} catch (error) {
console.log(`Error in pingCall IP4 for ${host}`, error);
}
return output;
};
/**
* @param {string} input
*/
function ExtractIPv6HostAndPort(input) {
const parts = input.split(":"); // Split by colons
const parts = input.split(":"); // Split by colons
// If there's a valid port at the end, extract it
const lastPart = parts[parts.length - 1];
const port = /^\d+$/.test(lastPart) ? parseInt(parts.pop(), 10) : null; // Check if last part is a number
// If there's a valid port at the end, extract it
const lastPart = parts[parts.length - 1];
const port = /^\d+$/.test(lastPart) ? parseInt(parts.pop(), 10) : null; // Check if last part is a number
// Reconstruct the IPv6 address
const host = parts.join(":");
// Reconstruct the IPv6 address
const host = parts.join(":");
// Ensure it's a valid IPv6 format
if (host.includes(":")) {
return { host, port }; // Port may be null if not present
}
return null; // Return null if the format is incorrect
// Ensure it's a valid IPv6 format
if (host.includes(":")) {
return { host, port }; // Port may be null if not present
}
return null; // Return null if the format is incorrect
}
export { TCP, ExtractIPv6HostAndPort, Ping };
+5
View File
@@ -6,11 +6,16 @@ import db from "../db/db.js";
async function waitForDataAndReturn(arr, ts, d, maxTime) {
await Wait(d);
let data = await db.getLastStatusBeforeCombined(arr, ts);
if (data) {
data.type = REALTIME;
return data;
} else if (d > maxTime) {
data = await db.getLastStatusBeforeCombined(arr, ts, ts - 86400);
if (data) {
return data;
}
return {
status: DOWN,
latency: 0,