diff --git a/Dockerfile b/Dockerfile
index f3e40890c..951c608e4 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -13,8 +13,8 @@ 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 \
+ sqlite3 \
+ libsqlite3-dev \
make=4.3-4.1 \
node-gyp=9.3.0-2 \
g++=4:12.2.0-3 \
@@ -77,7 +77,7 @@ 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 \
+ sqlite3 \
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 && \
diff --git a/docker-compose.yml b/docker-compose.yml
index a19bfabb4..3ad6a9fc7 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -8,12 +8,12 @@ version: '3.8'
services:
kener:
- image: rajnandan1/kener:latest # Change to 'rajnandan1/kener:alpine' for an even smaller image! 😁🚀
+ build: .
container_name: kener
- # 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
- KENER_SECRET_KEY: replace_me_with_a_random_string # Keep private!! - best to define in `.env` file or through Docker Secret
+ env_file: .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: America/Sao_Paulo
+ # KENER_SECRET_KEY: # 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:
@@ -28,7 +28,7 @@ services:
- '3000:3000/tcp'
volumes:
- data:/app/database # We suggest using a Docker named volume, which is more performant for databases
- - $(pwd)/uploads:/app/uploads
+ - ./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
@@ -36,25 +36,25 @@ services:
# - 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
+ # 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
+ #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:
diff --git a/openapi.json b/openapi.json
index c880024fb..942ff2359 100644
--- a/openapi.json
+++ b/openapi.json
@@ -32,6 +32,10 @@
{
"name": "Incidents",
"description": "APIs to integrate incidents"
+ },
+ {
+ "name": "Reports",
+ "description": "APIs to generate and retrieve reports"
}
],
"components": {
@@ -292,6 +296,150 @@
"start_date_time": 1731901920,
"title": "title of the incident"
}
+ },
+ "DowntimeReport": {
+ "type": "object",
+ "description": "Downtime report for a monitor",
+ "properties": {
+ "success": {
+ "type": "boolean",
+ "example": true
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "monitor": {
+ "type": "object",
+ "properties": {
+ "tag": {
+ "type": "string",
+ "example": "earth"
+ },
+ "name": {
+ "type": "string",
+ "example": "Earth API"
+ },
+ "monitor_type": {
+ "type": "string",
+ "example": "HTTP"
+ }
+ }
+ },
+ "period": {
+ "type": "object",
+ "properties": {
+ "start": {
+ "type": "integer",
+ "description": "Start timestamp in seconds",
+ "example": 1712448000
+ },
+ "end": {
+ "type": "integer",
+ "description": "End timestamp in seconds",
+ "example": 1712534400
+ },
+ "startFormatted": {
+ "type": "string",
+ "example": "2024-04-07 00:00:00"
+ },
+ "endFormatted": {
+ "type": "string",
+ "example": "2024-04-08 00:00:00"
+ },
+ "durationHours": {
+ "type": "number",
+ "example": 24
+ }
+ }
+ },
+ "summary": {
+ "type": "object",
+ "properties": {
+ "totalDowntimeMinutes": {
+ "type": "number",
+ "example": 75.5
+ },
+ "totalDowntimeHours": {
+ "type": "number",
+ "example": 1.26
+ },
+ "uptimePercentage": {
+ "type": "number",
+ "example": 99.4792
+ },
+ "downtimePercentage": {
+ "type": "number",
+ "example": 0.5208
+ },
+ "totalEvents": {
+ "type": "integer",
+ "example": 2
+ },
+ "alertsGenerated": {
+ "type": "integer",
+ "example": 1
+ }
+ }
+ },
+ "downtimes": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "startDateTime": {
+ "type": "string",
+ "example": "2024-04-07 14:30:00"
+ },
+ "endDateTime": {
+ "type": "string",
+ "example": "2024-04-07 15:45:00"
+ },
+ "startTimestamp": {
+ "type": "integer",
+ "example": 1712500200
+ },
+ "endTimestamp": {
+ "type": "integer",
+ "example": 1712504700
+ },
+ "durationMinutes": {
+ "type": "number",
+ "example": 75
+ },
+ "durationHours": {
+ "type": "number",
+ "example": 1.25
+ },
+ "status": {
+ "type": "string",
+ "enum": ["DOWN", "DEGRADED"],
+ "example": "DOWN"
+ },
+ "alertGenerated": {
+ "type": "boolean",
+ "example": true
+ }
+ }
+ }
+ }
+ }
+ },
+ "meta": {
+ "type": "object",
+ "properties": {
+ "requestedAt": {
+ "type": "integer",
+ "description": "Timestamp when report was requested",
+ "example": 1712600000
+ },
+ "processingTimeMs": {
+ "type": "integer",
+ "description": "Time taken to generate report in milliseconds",
+ "example": 145
+ }
+ }
+ }
+ }
}
},
"responses": {
@@ -1995,6 +2143,164 @@
}
}
]
+ },
+ "/api/reports/downtime": {
+ "get": {
+ "tags": ["Reports"],
+ "summary": "Get downtime report for a monitor",
+ "description": "Returns detailed downtime information for a specific monitor within a date range. Requires API key authentication.",
+ "security": [
+ {
+ "bearerAuth": []
+ }
+ ],
+ "parameters": [
+ {
+ "name": "tag",
+ "in": "query",
+ "required": true,
+ "schema": {
+ "type": "string"
+ },
+ "description": "Monitor tag identifier",
+ "example": "earth"
+ },
+ {
+ "name": "start",
+ "in": "query",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ },
+ "description": "Start timestamp in Unix seconds (UTC)",
+ "example": 1712448000
+ },
+ {
+ "name": "end",
+ "in": "query",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ },
+ "description": "End timestamp in Unix seconds (UTC)",
+ "example": 1712534400
+ },
+ {
+ "name": "format",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "type": "string",
+ "enum": ["json", "summary"]
+ },
+ "description": "Response format - 'json' for full report or 'summary' for summary only",
+ "example": "json"
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful response with downtime report",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/DowntimeReport"
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Bad request - missing or invalid parameters",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "success": {
+ "type": "boolean",
+ "example": false
+ },
+ "error": {
+ "type": "object",
+ "properties": {
+ "code": {
+ "type": "string",
+ "enum": ["MISSING_PARAMETERS", "INVALID_PARAMETERS", "INVALID_DATE_RANGE"],
+ "example": "MISSING_PARAMETERS"
+ },
+ "message": {
+ "type": "string",
+ "example": "Missing required parameters: tag, start, end"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "$ref": "#/components/responses/Response401"
+ },
+ "404": {
+ "description": "Monitor not found",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "success": {
+ "type": "boolean",
+ "example": false
+ },
+ "error": {
+ "type": "object",
+ "properties": {
+ "code": {
+ "type": "string",
+ "example": "MONITOR_NOT_FOUND"
+ },
+ "message": {
+ "type": "string",
+ "example": "Monitor earth not found"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Internal server error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "success": {
+ "type": "boolean",
+ "example": false
+ },
+ "error": {
+ "type": "object",
+ "properties": {
+ "code": {
+ "type": "string",
+ "example": "INTERNAL_ERROR"
+ },
+ "message": {
+ "type": "string",
+ "example": "Error generating report"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
}
}
}
diff --git a/package-lock.json b/package-lock.json
index 2fc774ee6..84a081672 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -50,6 +50,7 @@
"node-cache": "^5.1.2",
"nodemailer": "^6.10.0",
"npm-run-all": "^4.1.5",
+ "papaparse": "^5.5.3",
"pg": "^8.13.1",
"pg-pool": "^3.7.0",
"ping": "^0.4.4",
@@ -6225,6 +6226,12 @@
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
"license": "BlueOak-1.0.0"
},
+ "node_modules/papaparse": {
+ "version": "5.5.3",
+ "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.5.3.tgz",
+ "integrity": "sha512-5QvjGxYVjxO59MGU2lHVYpRWBBtKHnlIAcSe1uNFCkkptUh63NFRj0FJQm7nR67puEruUci/ZkjmEFrjCAyP4A==",
+ "license": "MIT"
+ },
"node_modules/parse-json": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
diff --git a/package.json b/package.json
index ada1209fd..df95770a7 100644
--- a/package.json
+++ b/package.json
@@ -108,6 +108,7 @@
"node-cache": "^5.1.2",
"nodemailer": "^6.10.0",
"npm-run-all": "^4.1.5",
+ "papaparse": "^5.5.3",
"pg": "^8.13.1",
"pg-pool": "^3.7.0",
"ping": "^0.4.4",
diff --git a/src/lib/components/manage/homePage.svelte b/src/lib/components/manage/homePage.svelte
index 66169dbb5..2e8f83948 100644
--- a/src/lib/components/manage/homePage.svelte
+++ b/src/lib/components/manage/homePage.svelte
@@ -1,4 +1,5 @@
@@ -288,10 +331,10 @@
{#if rollerLoading}
{/if}
- {#if !isNaN(uptimesRollers[rolledAt].value)}
+ {#if uptimesRollers[rolledAt]?.value !== undefined && uptimesRollers[rolledAt]?.value !== '-'}
{#each dayIncidentsFull as incident, index}
-
+
{/each}
diff --git a/src/lib/locales/en.json b/src/lib/locales/en.json
index 20a221636..2352040c5 100644
--- a/src/lib/locales/en.json
+++ b/src/lib/locales/en.json
@@ -13,6 +13,13 @@
"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",
+ "Not all incidents are shown. Click to see more": "Not all incidents are shown. Click to see more",
+ "Auto-Refresh": "Auto-Refresh",
+ "auto-refresh-description": "Enable to periodically refresh the page data.",
+ "Interval": "Interval",
+ "Seconds": "Seconds",
+ "Enable": "Enable",
+ "Disable": "Disable",
"Back": "Back",
"Badge Copied": "Badge Copied",
"Badge": "Badge",
@@ -88,5 +95,35 @@
"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"
+ "You are logged in as %email": "You are logged in as %email",
+ "Reports": "Reports",
+ "Downtime Reports": "Downtime Reports",
+ "Generate Report": "Generate Report",
+ "Export CSV": "Export CSV",
+ "Select monitor": "Select monitor",
+ "Start Date": "Start Date",
+ "End Date": "End Date",
+ "Total Downtime": "Total Downtime",
+ "Downtime Events": "Downtime Events",
+ "Alerts Generated": "Alerts Generated",
+ "Generate detailed downtime reports for your monitors with customizable date ranges": "Generate detailed downtime reports for your monitors with customizable date ranges",
+ "Select monitor and date range to generate downtime report": "Select monitor and date range to generate downtime report",
+ "Monitor": "Monitor",
+ "Period": "Period",
+ "Summary": "Summary",
+ "Downtime": "Downtime",
+ "Start Date/Time": "Start Date/Time",
+ "End Date/Time": "End Date/Time",
+ "Duration (Minutes)": "Duration (Minutes)",
+ "Duration (Hours)": "Duration (Hours)",
+ "Alert Generated": "Alert Generated",
+ "Alert": "Alert",
+ "No downtime events found!": "No downtime events found!",
+ "This monitor had 100% uptime during the selected period.": "This monitor had 100% uptime during the selected period.",
+ "Please select a monitor": "Please select a monitor",
+ "Error generating report": "Error generating report",
+ "Error exporting CSV": "Error exporting CSV",
+ "event": "event",
+ "events": "events",
+ "found": "found"
}
diff --git a/src/lib/locales/pt-BR.json b/src/lib/locales/pt-BR.json
index c340fd894..082897683 100644
--- a/src/lib/locales/pt-BR.json
+++ b/src/lib/locales/pt-BR.json
@@ -13,6 +13,8 @@
"All Systems are Down": "Todos os Sistemas estão Indisponíveis",
"All Systems are Operational": "Todos os Sistemas estão Operacionais",
"All Systems are in Maintenance": "Todos os Sistemas estão em Manutenção",
+ "Auto-Refresh": "Atualização Automática",
+ "auto-refresh-description": "Ative para atualizar os dados da página periodicamente.",
"Back": "Voltar",
"Badge Copied": "Emblema Copiado",
"Badge": "Emblema",
@@ -88,5 +90,35 @@
"Uptime": "Tempo de Atividade",
"View in detail": "Ver em detalhes",
"We have sent a code to your email. Please enter it below to confirm your login": "Enviamos um código para seu e-mail. Por favor, digite-o abaixo para confirmar seu login",
- "You are logged in as %email": "Você está logado como %email"
+ "You are logged in as %email": "Você está logado como %email",
+ "Reports": "Relatórios",
+ "Downtime Reports": "Relatórios de Indisponibilidade",
+ "Generate Report": "Gerar Relatório",
+ "Export CSV": "Exportar CSV",
+ "Select monitor": "Selecionar monitor",
+ "Start Date": "Data Inicial",
+ "End Date": "Data Final",
+ "Total Downtime": "Tempo Total de Indisponibilidade",
+ "Downtime Events": "Eventos de Indisponibilidade",
+ "Alerts Generated": "Alertas Gerados",
+ "Generate detailed downtime reports for your monitors with customizable date ranges": "Gere relatórios detalhados de indisponibilidade para seus monitores com períodos personalizáveis",
+ "Select monitor and date range to generate downtime report": "Selecione o monitor e o período para gerar o relatório de indisponibilidade",
+ "Monitor": "Monitor",
+ "Period": "Período",
+ "Summary": "Resumo",
+ "Downtime": "Indisponibilidade",
+ "Start Date/Time": "Data/Hora Inicial",
+ "End Date/Time": "Data/Hora Final",
+ "Duration (Minutes)": "Duração (Minutos)",
+ "Duration (Hours)": "Duração (Horas)",
+ "Alert Generated": "Alerta Gerado",
+ "Alert": "Alerta",
+ "No downtime events found!": "Nenhum evento de indisponibilidade encontrado!",
+ "This monitor had 100% uptime during the selected period.": "Este monitor teve 100% de disponibilidade durante o período selecionado.",
+ "Please select a monitor": "Por favor, selecione um monitor",
+ "Error generating report": "Erro ao gerar relatório",
+ "Error exporting CSV": "Erro ao exportar CSV",
+ "event": "evento",
+ "events": "eventos",
+ "found": "encontrado"
}
diff --git a/src/lib/server/controllers/reports.js b/src/lib/server/controllers/reports.js
new file mode 100644
index 000000000..682542c9e
--- /dev/null
+++ b/src/lib/server/controllers/reports.js
@@ -0,0 +1,164 @@
+// @ts-nocheck
+import db from "../db/db.js";
+import { InterpolateData, GetLastStatusBefore } from "./controller.js";
+import { format } from "date-fns";
+
+/**
+ * Generate downtime report for a monitor within a date range
+ * @param {string} monitor_tag - Monitor tag
+ * @param {number} startTimestamp - Start timestamp in seconds
+ * @param {number} endTimestamp - End timestamp in seconds
+ * @returns {Promise