mirror of
https://github.com/rajnandan1/kener.git
synced 2026-01-06 01:20:15 -06:00
@@ -77,6 +77,7 @@ Copy and update the translation file in the `locales` folder. The translation fi
|
||||
"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",
|
||||
|
||||
35
knexfile.js
35
knexfile.js
@@ -9,28 +9,29 @@ const databaseType = databaseURLParts[0];
|
||||
const databasePath = databaseURLParts[1];
|
||||
|
||||
const knexOb = {
|
||||
migrations: {
|
||||
directory: "./migrations"
|
||||
},
|
||||
seeds: {
|
||||
directory: "./seeds"
|
||||
}
|
||||
migrations: {
|
||||
directory: "./migrations",
|
||||
},
|
||||
seeds: {
|
||||
directory: "./seeds",
|
||||
},
|
||||
databaseType,
|
||||
};
|
||||
if (databaseType === "sqlite") {
|
||||
knexOb.client = "better-sqlite3";
|
||||
knexOb.connection = {
|
||||
filename: databasePath
|
||||
};
|
||||
knexOb.useNullAsDefault = true;
|
||||
knexOb.client = "better-sqlite3";
|
||||
knexOb.connection = {
|
||||
filename: databasePath,
|
||||
};
|
||||
knexOb.useNullAsDefault = true;
|
||||
} else if (databaseType === "postgresql") {
|
||||
knexOb.client = "pg";
|
||||
knexOb.connection = databaseURL;
|
||||
knexOb.client = "pg";
|
||||
knexOb.connection = databaseURL;
|
||||
} else if (databaseType === "mysql") {
|
||||
knexOb.client = "mysql2";
|
||||
knexOb.connection = databaseURL;
|
||||
knexOb.client = "mysql2";
|
||||
knexOb.connection = databaseURL;
|
||||
} else {
|
||||
console.error("Invalid database type");
|
||||
process.exit(1);
|
||||
console.error("Invalid database type");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
export default knexOb;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "kener",
|
||||
"version": "3.2.1",
|
||||
"version": "3.2.2",
|
||||
"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.",
|
||||
|
||||
@@ -41,7 +41,8 @@
|
||||
.squares-pattern::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
background-image: linear-gradient(#444cf7 1px, transparent 1px),
|
||||
background-image:
|
||||
linear-gradient(#444cf7 1px, transparent 1px),
|
||||
linear-gradient(to right, #444cf7 1px, var(--background-kener) 1px);
|
||||
-webkit-mask-image: linear-gradient(
|
||||
180deg,
|
||||
@@ -87,7 +88,8 @@
|
||||
rgba(0, 0, 0, 0.03125) 80%,
|
||||
rgba(0, 0, 0, 0) 100%
|
||||
);
|
||||
background-image: linear-gradient(#616fbf 1px, transparent 1px),
|
||||
background-image:
|
||||
linear-gradient(#616fbf 1px, transparent 1px),
|
||||
linear-gradient(to right, #616fbf 1px, var(--background-kener) 1px);
|
||||
}
|
||||
|
||||
@@ -476,7 +478,8 @@ textarea::placeholder {
|
||||
}
|
||||
|
||||
.mesh {
|
||||
background-image: linear-gradient(
|
||||
background-image:
|
||||
linear-gradient(
|
||||
323deg,
|
||||
rgba(255, 255, 255, 0.01) 0%,
|
||||
rgba(255, 255, 255, 0.01) 16.667%,
|
||||
@@ -607,7 +610,8 @@ textarea::placeholder {
|
||||
}
|
||||
|
||||
.dark .mesh {
|
||||
background-image: linear-gradient(
|
||||
background-image:
|
||||
linear-gradient(
|
||||
158deg,
|
||||
rgba(84, 84, 84, 0.03) 0%,
|
||||
rgba(84, 84, 84, 0.03) 20%,
|
||||
@@ -821,7 +825,8 @@ textarea::placeholder {
|
||||
}
|
||||
|
||||
.button-77:active:not(:disabled):after {
|
||||
background-image: linear-gradient(0deg, rgba(0, 0, 0, 0.2), rgba(0, 0, 0, 0.2)),
|
||||
background-image:
|
||||
linear-gradient(0deg, rgba(0, 0, 0, 0.2), rgba(0, 0, 0, 0.2)),
|
||||
linear-gradient(92.83deg, #ff7426 0, #f93a13 100%);
|
||||
bottom: 4px;
|
||||
left: 4px;
|
||||
@@ -872,3 +877,31 @@ textarea::placeholder {
|
||||
transform: rotate(15deg);
|
||||
}
|
||||
}
|
||||
.dots-animation::after {
|
||||
content: ".";
|
||||
animation: dots 1.5s steps(4) infinite;
|
||||
}
|
||||
|
||||
@keyframes dots {
|
||||
0% {
|
||||
content: ".";
|
||||
}
|
||||
16% {
|
||||
content: "..";
|
||||
}
|
||||
32% {
|
||||
content: "...";
|
||||
}
|
||||
48% {
|
||||
content: "....";
|
||||
}
|
||||
64% {
|
||||
content: "...";
|
||||
}
|
||||
80% {
|
||||
content: "..";
|
||||
}
|
||||
100% {
|
||||
content: ".";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
<script>
|
||||
import { formatDistanceToNow, formatDistance } from "date-fns";
|
||||
import { Settings } from "lucide-svelte";
|
||||
import { Settings, ArrowRight } from "lucide-svelte";
|
||||
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";
|
||||
export let incident;
|
||||
export let index;
|
||||
export let lang;
|
||||
export let selectedLang = "en";
|
||||
let startTime = new Date(incident.start_date_time * 1000);
|
||||
let endTime = new Date();
|
||||
let nowTime = new Date();
|
||||
@@ -18,13 +18,17 @@
|
||||
endTime = new Date(incident.end_date_time * 1000);
|
||||
}
|
||||
let incidentType = incident.incident_type;
|
||||
const lastedFor = fd(startTime, endTime, selectedLang);
|
||||
const startedAt = fdn(startTime, selectedLang);
|
||||
const lastedFor = fd(startTime, endTime, $page.data.selectedLang);
|
||||
const remainingTime = fd(nowTime, endTime, $page.data.selectedLang);
|
||||
const startedAt = fdn(startTime, $page.data.selectedLang);
|
||||
|
||||
let isFuture = false;
|
||||
//is future incident
|
||||
let incidentTimeStatus = "";
|
||||
if (nowTime < startTime) {
|
||||
isFuture = true;
|
||||
incidentTimeStatus = "YET_TO_START";
|
||||
} else if (nowTime >= startTime && nowTime <= endTime) {
|
||||
incidentTimeStatus = "ONGOING";
|
||||
} else if (nowTime > endTime) {
|
||||
incidentTimeStatus = "COMPLETED";
|
||||
}
|
||||
|
||||
let accordionValue = "incident-0";
|
||||
@@ -37,30 +41,36 @@
|
||||
let incidentDateSummary = "";
|
||||
let maintenanceBadge = "";
|
||||
let maintenanceBadgeColor = "";
|
||||
if (!isFuture && incident.state != "RESOLVED") {
|
||||
incidentDateSummary = l(lang, "Started %startedAt, still ongoing", {
|
||||
|
||||
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
|
||||
});
|
||||
maintenanceBadge = "Maintenance in Progress";
|
||||
maintenanceBadgeColor = "text-maintenance-in-progress";
|
||||
} else if (!isFuture && incident.state == "RESOLVED") {
|
||||
incidentDateSummary = l(lang, "Started %startedAt, lasted for %lastedFor", {
|
||||
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";
|
||||
} else if (isFuture && incident.state != "RESOLVED") {
|
||||
incidentDateSummary = l(lang, "Starts %startedAt", { startedAt });
|
||||
maintenanceBadge = "Upcoming Maintenance";
|
||||
maintenanceBadgeColor = "text-upcoming-maintenance";
|
||||
} else if (isFuture && incident.state == "RESOLVED") {
|
||||
incidentDateSummary = l(lang, "Starts %startedAt, will last for %lastedFor", {
|
||||
startedAt,
|
||||
lastedFor
|
||||
});
|
||||
maintenanceBadge = "Upcoming Maintenance";
|
||||
maintenanceBadgeColor = "text-upcoming-maintenance";
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -73,11 +83,11 @@
|
||||
<p class="flex gap-x-2 text-xs font-semibold">
|
||||
{#if incidentType == "INCIDENT"}
|
||||
<span class="badge-{incident.state}">
|
||||
{l(lang, incident.state)}
|
||||
{l($page.data.lang, incident.state)}
|
||||
</span>
|
||||
{:else if incidentType == "MAINTENANCE"}
|
||||
<span class="{maintenanceBadgeColor} ">
|
||||
{l(lang, maintenanceBadge)}
|
||||
{l($page.data.lang, maintenanceBadge)}
|
||||
</span>
|
||||
{/if}
|
||||
{#if $page.data.isLoggedIn}
|
||||
@@ -91,20 +101,48 @@
|
||||
{/if}
|
||||
</p>
|
||||
|
||||
<p class="scroll-m-20 text-lg font-medium tracking-tight">
|
||||
<p class="font-medium">
|
||||
{incident.title}
|
||||
</p>
|
||||
{#if !!incidentDateSummary}
|
||||
<p class="scroll-m-20 text-sm font-medium tracking-wide text-muted-foreground">
|
||||
{incidentDateSummary}
|
||||
</p>
|
||||
<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 gap-2">
|
||||
<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
|
||||
@@ -123,7 +161,7 @@
|
||||
</div>
|
||||
{/if}
|
||||
<p class="my-3 text-xs font-semibold uppercase text-muted-foreground">
|
||||
{l(lang, "Updates")}
|
||||
{l($page.data.lang, "Updates")}
|
||||
</p>
|
||||
{#if incident.comments.length > 0}
|
||||
{#if incidentType == "INCIDENT"}
|
||||
@@ -133,14 +171,14 @@
|
||||
<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(lang, comment.state)}
|
||||
{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",
|
||||
selectedLang,
|
||||
$page.data.selectedLang,
|
||||
$page.data.localTz
|
||||
)}
|
||||
</time>
|
||||
@@ -159,7 +197,7 @@
|
||||
{f(
|
||||
new Date(comment.commented_at * 1000),
|
||||
"MMMM do yyyy, h:mm:ss a",
|
||||
selectedLang,
|
||||
$page.data.selectedLang,
|
||||
$page.data.localTz
|
||||
)}
|
||||
</time>
|
||||
@@ -173,7 +211,7 @@
|
||||
{/if}
|
||||
{:else}
|
||||
<p class="text-sm font-medium">
|
||||
{l(lang, "No Updates Yet")}
|
||||
{l($page.data.lang, "No Updates Yet")}
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -149,6 +149,14 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (newTrigger.trigger_meta.has_webhook_body) {
|
||||
if (newTrigger.trigger_meta.webhook_body == "") {
|
||||
invalidFormMessage = "Webhook Body is required";
|
||||
formState = "idle";
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
//newTrigger.name present not empty
|
||||
if (newTrigger.name == "") {
|
||||
@@ -423,7 +431,7 @@
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
class="peer relative h-6 w-11 rounded-full bg-gray-200 after:absolute after:start-[2px] after:top-[2px] after:h-5 after:w-5 after:rounded-full after:border after:border-gray-300 after:bg-white after:transition-all after:content-[''] peer-checked:bg-blue-600 peer-checked:after:translate-x-full peer-checked:after:border-white peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 rtl:peer-checked:after:-translate-x-full dark:border-gray-600 dark:bg-gray-700 dark:peer-focus:ring-blue-800"
|
||||
class="peer relative h-6 w-11 rounded-full bg-gray-200 after:absolute after:start-[2px] after:top-[2px] after:h-5 after:w-5 after:rounded-full after:border after:border-gray-300 after:bg-white after:transition-all after:content-[''] peer-checked:bg-blue-600 peer-checked:after:translate-x-full peer-checked:after:border-white peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 dark:border-gray-600 dark:bg-gray-700 dark:peer-focus:ring-blue-800 rtl:peer-checked:after:-translate-x-full"
|
||||
></div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
"Standard": "Standard",
|
||||
"Started %startedAt, lasted for %lastedFor": "Begann vor etwa %startedAt und dauerte etwa %lastedFor",
|
||||
"Started %startedAt, still ongoing": "Begann vor etwa %startedAt und dauert noch an",
|
||||
"Started %startedAt, will last for %lastedFor more": "Begann vor etwa %startedAt und wird noch etwa %lastedFor dauern",
|
||||
"Starts %startedAt, will last for %lastedFor": "Beginnt in %startedAt und wird etwa %lastedFor dauern",
|
||||
"Starts %startedAt": "Beginnt in %startedAt",
|
||||
"Status OK": "Status OK",
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
"Standard": "Standard",
|
||||
"Started %startedAt, lasted for %lastedFor": "Startede for cirka %startedAt siden, varede i cirka %lastedFor",
|
||||
"Started %startedAt, still ongoing": "Startede for cirka %startedAt siden, stadig igangværende",
|
||||
"Started %startedAt, will last for %lastedFor more": "Startede for cirka %startedAt siden, vil vare i cirka %lastedFor mere",
|
||||
"Starts %startedAt, will last for %lastedFor": "Starter om %startedAt, vil vare i cirka %lastedFor",
|
||||
"Starts %startedAt": "Starter om %startedAt",
|
||||
"Status OK": "Status OK",
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
"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",
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
"Standard": "Standard",
|
||||
"Started %startedAt, lasted for %lastedFor": "Commencé il y a environ %startedAt, a duré environ %lastedFor",
|
||||
"Started %startedAt, still ongoing": "Commencé il y a environ %startedAt, toujours en cours",
|
||||
"Started %startedAt, will last for %lastedFor more": "Commencé il y a environ %startedAt, durera environ %lastedFor de plus",
|
||||
"Starts %startedAt, will last for %lastedFor": "Commence dans %startedAt, durera environ %lastedFor",
|
||||
"Starts %startedAt": "Commence dans %startedAt",
|
||||
"Status OK": "Statut OK",
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
"Standard": "मानक",
|
||||
"Started %startedAt, lasted for %lastedFor": "%startedAt शुरू हुआ, %lastedFor तक चला",
|
||||
"Started %startedAt, still ongoing": "%startedAt शुरू हुआ, अभी भी जारी है",
|
||||
"Started %startedAt, will last for %lastedFor more": "%startedAt शुरू होगा, %lastedFor और चलेगा",
|
||||
"Starts %startedAt, will last for %lastedFor": "%startedAt में शुरू होगा, %lastedFor तक चलेगा",
|
||||
"Starts %startedAt": "%startedAt में शुरू होगा",
|
||||
"Status OK": "स्थिति ठीक है",
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
"Standard": "標準",
|
||||
"Started %startedAt, lasted for %lastedFor": "%startedAt 前に開始し、約 %lastedFor 続きました",
|
||||
"Started %startedAt, still ongoing": "%startedAt 前に開始し、まだ進行中です",
|
||||
"Started %startedAt, will last for %lastedFor more": "%startedAt 前に開始し、さらに %lastedFor 続きます",
|
||||
"Starts %startedAt, will last for %lastedFor": "%startedAt 後に開始し、約 %lastedFor 続きます",
|
||||
"Starts %startedAt": "%startedAt 後に開始",
|
||||
"Status OK": "ステータス正常",
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
"Standard": "기본",
|
||||
"Started %startedAt, lasted for %lastedFor": "%startedAt 전에 시작되었으며, %lastedFor 동안 지속되었습니다",
|
||||
"Started %startedAt, still ongoing": "%startedAt 전에 시작되었으며, 아직 진행중입니다",
|
||||
"Started %startedAt, will last for %lastedFor more": "%startedAt 전에 시작되었으며, %lastedFor 더 지속될 예정입니다",
|
||||
"Starts %startedAt, will last for %lastedFor": "%startedAt 전에 시작되었으며, %lastedFor 까지 지속될 예정입니다",
|
||||
"Starts %startedAt": "%startedAt 에 시작되었습니다",
|
||||
"Status OK": "양호",
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
"Standard": "Standard",
|
||||
"Started %startedAt, lasted for %lastedFor": "Startet %startedAt, varte i %lastedFor",
|
||||
"Started %startedAt, still ongoing": "Startet %startedAt, fortsatt pågår",
|
||||
"Started %startedAt, will last for %lastedFor more": "Startet %startedAt, vil vare i %lastedFor til",
|
||||
"Starts %startedAt, will last for %lastedFor": "Starter %startedAt, vil vare i %lastedFor",
|
||||
"Starts %startedAt": "Starter %startedAt",
|
||||
"Status OK": "Status OK",
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
"Standard": "Standaard",
|
||||
"Started %startedAt, lasted for %lastedFor": "Begonnen ongeveer %startedAt geleden, duurde ongeveer %lastedFor",
|
||||
"Started %startedAt, still ongoing": "Begonnen ongeveer %startedAt geleden, nog steeds bezig",
|
||||
"Started %startedAt, will last for %lastedFor more": "Begint %startedAt, zal nog ongeveer %lastedFor duren",
|
||||
"Starts %startedAt, will last for %lastedFor": "Begint over %startedAt, zal ongeveer %lastedFor duren",
|
||||
"Starts %startedAt": "Begint over %startedAt",
|
||||
"Status OK": "Status OK",
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
"Standard": "Padrão",
|
||||
"Started %startedAt, lasted for %lastedFor": "Ocorreu faz %startedAt e durou %lastedFor",
|
||||
"Started %startedAt, still ongoing": "Iniciou faz %startedAt, ainda em andamento",
|
||||
"Started %startedAt, will last for %lastedFor more": "Iniciará em %startedAt, durará mais %lastedFor",
|
||||
"Starts %startedAt, will last for %lastedFor": "Terá início em %startedAt, com duração de %lastedFor",
|
||||
"Starts %startedAt": "Inicia em %startedAt",
|
||||
"Status OK": "Status OK",
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
"Standard": "Стандартная",
|
||||
"Started %startedAt, lasted for %lastedFor": "Началось около %startedAt назад, длилось около %lastedFor",
|
||||
"Started %startedAt, still ongoing": "Началось около %startedAt назад, все еще продолжается",
|
||||
"Started %startedAt, will last for %lastedFor more": "Начнется около %startedAt, продлится еще около %lastedFor",
|
||||
"Starts %startedAt, will last for %lastedFor": "Начнется через %startedAt, продлится около %lastedFor",
|
||||
"Starts %startedAt": "Начнется через %startedAt",
|
||||
"Status OK": "Статус ОК",
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
"Standard": "Standart",
|
||||
"Started %startedAt, lasted for %lastedFor": "%startedAt başladı, %lastedFor sürdü",
|
||||
"Started %startedAt, still ongoing": "%startedAt başladı, devam ediyor",
|
||||
"Started %startedAt, will last for %lastedFor more": "%startedAt başladı, %lastedFor daha sürecek",
|
||||
"Starts %startedAt, will last for %lastedFor": "%startedAt başlıyor, %lastedFor sürecek",
|
||||
"Starts %startedAt": "%startedAt başlıyor",
|
||||
"Status OK": "Durum UYGUN",
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
"Standard": "Tiêu chuẩn",
|
||||
"Started %startedAt, lasted for %lastedFor": "Bắt đầu khoảng %startedAt trước, kéo dài khoảng %lastedFor",
|
||||
"Started %startedAt, still ongoing": "Bắt đầu khoảng %startedAt trước, vẫn đang diễn ra",
|
||||
"Started %startedAt, will last for %lastedFor more": "Bắt đầu khoảng %startedAt trước, sẽ kéo dài thêm khoảng %lastedFor",
|
||||
"Starts %startedAt, will last for %lastedFor": "Bắt đầu trong %startedAt, sẽ kéo dài khoảng %lastedFor",
|
||||
"Starts %startedAt": "Bắt đầu trong %startedAt",
|
||||
"Status OK": "Trạng thái OK",
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
"Standard": "标准",
|
||||
"Started %startedAt, lasted for %lastedFor": "大约 %startedAt 前开始,持续了约 %lastedFor",
|
||||
"Started %startedAt, still ongoing": "大约 %startedAt 前开始,目前仍在进行中",
|
||||
"Started %startedAt, will last for %lastedFor more": "大约 %startedAt 前开始,将持续约 %lastedFor",
|
||||
"Starts %startedAt, will last for %lastedFor": "%startedAt 后开始,将持续约 %lastedFor",
|
||||
"Starts %startedAt": "%startedAt 后开始",
|
||||
"Status OK": "状态正常",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// @ts-nocheck
|
||||
import { GetMinuteStartNowTimestampUTC } from "../tool.js";
|
||||
import { GetMinuteStartNowTimestampUTC, GetDbType } from "../tool.js";
|
||||
import { MANUAL, SIGNAL } from "../constants.js";
|
||||
import Knex from "knex";
|
||||
|
||||
@@ -458,20 +458,39 @@ class DbImpl {
|
||||
}
|
||||
|
||||
async createIncident(data) {
|
||||
const [incident] = await this.knex("incidents")
|
||||
.insert({
|
||||
title: data.title,
|
||||
start_date_time: data.start_date_time,
|
||||
end_date_time: data.end_date_time,
|
||||
status: data.status,
|
||||
state: data.state,
|
||||
created_at: this.knex.fn.now(),
|
||||
updated_at: this.knex.fn.now(),
|
||||
incident_type: data.incident_type,
|
||||
incident_source: data.incident_source,
|
||||
})
|
||||
.returning("*");
|
||||
return incident;
|
||||
const dbType = GetDbType(); // sqlite, postgresql, mysql
|
||||
|
||||
// Common insert data
|
||||
const insertData = {
|
||||
title: data.title,
|
||||
start_date_time: data.start_date_time,
|
||||
end_date_time: data.end_date_time,
|
||||
status: data.status,
|
||||
state: data.state,
|
||||
created_at: this.knex.fn.now(),
|
||||
updated_at: this.knex.fn.now(),
|
||||
incident_type: data.incident_type,
|
||||
incident_source: data.incident_source,
|
||||
};
|
||||
|
||||
// PostgreSQL supports returning clause
|
||||
if (dbType === "postgresql") {
|
||||
const [incident] = await this.knex("incidents").insert(insertData).returning("*");
|
||||
return incident;
|
||||
}
|
||||
// MySQL and SQLite need different approaches
|
||||
else {
|
||||
// Insert and get the ID
|
||||
const result = await this.knex("incidents").insert(insertData);
|
||||
|
||||
// Different handling for MySQL vs SQLite
|
||||
let id = result[0];
|
||||
|
||||
// Fetch the newly created record
|
||||
const incident = await this.knex("incidents").where("id", id).first();
|
||||
|
||||
return incident;
|
||||
}
|
||||
}
|
||||
|
||||
//get incidents paginated
|
||||
|
||||
@@ -1,93 +1,99 @@
|
||||
// @ts-nocheck
|
||||
class Discord {
|
||||
url;
|
||||
headers;
|
||||
method;
|
||||
siteData;
|
||||
monitorData;
|
||||
url;
|
||||
headers;
|
||||
method;
|
||||
siteData;
|
||||
monitorData;
|
||||
|
||||
constructor(url, siteData, monitorData) {
|
||||
const kenerHeader = {
|
||||
"Content-Type": "application/json",
|
||||
"User-Agent": "Kener"
|
||||
};
|
||||
constructor(url, siteData, monitorData) {
|
||||
const kenerHeader = {
|
||||
"Content-Type": "application/json",
|
||||
"User-Agent": "Kener",
|
||||
};
|
||||
|
||||
this.url = url;
|
||||
this.headers = Object.assign(kenerHeader, {});
|
||||
this.method = "POST";
|
||||
this.siteData = siteData;
|
||||
this.monitorData = monitorData;
|
||||
}
|
||||
this.url = url;
|
||||
this.headers = Object.assign(kenerHeader, {});
|
||||
this.method = "POST";
|
||||
this.siteData = siteData;
|
||||
this.monitorData = monitorData;
|
||||
}
|
||||
|
||||
transformData(data) {
|
||||
let siteURL = this.siteData.siteURL;
|
||||
let logo =
|
||||
this.siteData.siteURL +
|
||||
(!!process.env.KENER_BASE_PATH ? process.env.KENER_BASE_PATH : "") +
|
||||
this.siteData.logo;
|
||||
transformData(data) {
|
||||
let siteURL = this.siteData.siteURL;
|
||||
|
||||
let color = 13250616; //down;
|
||||
if (data.severity === "warning") {
|
||||
color = 15125089;
|
||||
}
|
||||
if (data.status === "RESOLVED") {
|
||||
color = 5156244;
|
||||
}
|
||||
return {
|
||||
username: this.siteData.siteName,
|
||||
avatar_url: logo,
|
||||
content: `## ${data.alert_name}\n${data.status === "TRIGGERED" ? "🔴 Triggered" : "🟢 Resolved"}\n${data.description}\nClick [here](${data.actions[0].url}) for more.`,
|
||||
embeds: [
|
||||
{
|
||||
title: data.alert_name,
|
||||
description: data.description,
|
||||
url: data.actions[0].url,
|
||||
color: color,
|
||||
fields: [
|
||||
{
|
||||
name: "Monitor",
|
||||
value: data.details.metric,
|
||||
inline: false
|
||||
},
|
||||
{
|
||||
name: "Alert ID",
|
||||
value: data.id,
|
||||
inline: false
|
||||
},
|
||||
{
|
||||
name: "Current Value",
|
||||
value: data.details.current_value,
|
||||
inline: true
|
||||
},
|
||||
{
|
||||
name: "Threshold",
|
||||
value: data.details.threshold,
|
||||
inline: true
|
||||
}
|
||||
],
|
||||
footer: {
|
||||
text: "Kener",
|
||||
icon_url: logo
|
||||
},
|
||||
timestamp: data.timestamp
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
//remove / if there at the end of the url
|
||||
if (siteURL.endsWith("/")) {
|
||||
siteURL = siteURL.slice(0, -1);
|
||||
}
|
||||
|
||||
async send(data) {
|
||||
try {
|
||||
const response = await fetch(this.url, {
|
||||
method: this.method,
|
||||
headers: this.headers,
|
||||
body: JSON.stringify(this.transformData(data))
|
||||
});
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error("Error sending webhook", error);
|
||||
return error;
|
||||
}
|
||||
}
|
||||
if (!this.siteData.logo.startsWith("http"))
|
||||
this.siteData.logo = `${siteURL}${!!process.env.KENER_BASE_PATH ? process.env.KENER_BASE_PATH : ""}${this.siteData.logo}`;
|
||||
|
||||
let logo = this.siteData.logo;
|
||||
|
||||
let color = 13250616; //down;
|
||||
if (data.severity === "warning") {
|
||||
color = 15125089;
|
||||
}
|
||||
if (data.status === "RESOLVED") {
|
||||
color = 5156244;
|
||||
}
|
||||
return {
|
||||
username: this.siteData.siteName,
|
||||
avatar_url: logo,
|
||||
content: `## ${data.alert_name}\n${data.status === "TRIGGERED" ? "🔴 Triggered" : "🟢 Resolved"}\n${data.description}\nClick [here](${data.actions[0].url}) for more.`,
|
||||
embeds: [
|
||||
{
|
||||
title: data.alert_name,
|
||||
description: data.description,
|
||||
url: data.actions[0].url,
|
||||
color: color,
|
||||
fields: [
|
||||
{
|
||||
name: "Monitor",
|
||||
value: data.details.metric,
|
||||
inline: false,
|
||||
},
|
||||
{
|
||||
name: "Alert ID",
|
||||
value: data.id,
|
||||
inline: false,
|
||||
},
|
||||
{
|
||||
name: "Current Value",
|
||||
value: data.details.current_value,
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: "Threshold",
|
||||
value: data.details.threshold,
|
||||
inline: true,
|
||||
},
|
||||
],
|
||||
footer: {
|
||||
text: "Kener",
|
||||
icon_url: logo,
|
||||
},
|
||||
timestamp: data.timestamp,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
async send(data) {
|
||||
try {
|
||||
const response = await fetch(this.url, {
|
||||
method: this.method,
|
||||
headers: this.headers,
|
||||
body: JSON.stringify(this.transformData(data)),
|
||||
});
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error("Error sending webhook", error);
|
||||
return error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Discord;
|
||||
|
||||
@@ -3,92 +3,90 @@
|
||||
import { GetRequiredSecrets, ReplaceAllOccurrences } from "../tool.js";
|
||||
|
||||
class Webhook {
|
||||
url;
|
||||
headers;
|
||||
method;
|
||||
siteData;
|
||||
monitorData;
|
||||
trigger_meta;
|
||||
url;
|
||||
headers;
|
||||
method;
|
||||
siteData;
|
||||
monitorData;
|
||||
trigger_meta;
|
||||
|
||||
constructor(trigger_meta, method, siteData, monitorData) {
|
||||
const kenerHeader = {
|
||||
"Content-Type": "application/json",
|
||||
"User-Agent": "Kener/3.0.0"
|
||||
};
|
||||
let headers = trigger_meta.headers;
|
||||
this.trigger_meta = trigger_meta;
|
||||
this.url = trigger_meta.url;
|
||||
this.headers = kenerHeader;
|
||||
for (let i = 0; i < headers.length; i++) {
|
||||
const header = headers[i];
|
||||
this.headers[header.key] = header.value;
|
||||
}
|
||||
this.method = "POST";
|
||||
this.siteData = siteData;
|
||||
this.monitorData = monitorData;
|
||||
constructor(trigger_meta, method, siteData, monitorData) {
|
||||
const kenerHeader = {
|
||||
"Content-Type": "application/json",
|
||||
"User-Agent": "Kener/3.2.2",
|
||||
};
|
||||
let headers = trigger_meta.headers;
|
||||
this.trigger_meta = trigger_meta;
|
||||
this.url = trigger_meta.url;
|
||||
this.headers = kenerHeader;
|
||||
for (let i = 0; i < headers.length; i++) {
|
||||
const header = headers[i];
|
||||
this.headers[header.key] = header.value;
|
||||
}
|
||||
this.method = "POST";
|
||||
this.siteData = siteData;
|
||||
this.monitorData = monitorData;
|
||||
|
||||
let envSecrets = GetRequiredSecrets(
|
||||
`${this.url} ${JSON.stringify(this.headers)} ${JSON.stringify(this.trigger_meta.webhook_body)}`
|
||||
);
|
||||
//replace secrets in url and headers
|
||||
let envSecrets = GetRequiredSecrets(
|
||||
`${this.url} ${JSON.stringify(this.headers)} ${JSON.stringify(this.trigger_meta.webhook_body)}`,
|
||||
);
|
||||
//replace secrets in url and headers
|
||||
|
||||
for (let i = 0; i < envSecrets.length; i++) {
|
||||
const secret = envSecrets[i];
|
||||
this.url = ReplaceAllOccurrences(this.url, secret.find, secret.replace);
|
||||
this.headers = JSON.parse(
|
||||
ReplaceAllOccurrences(JSON.stringify(this.headers), secret.find, secret.replace)
|
||||
);
|
||||
}
|
||||
for (let i = 0; i < envSecrets.length; i++) {
|
||||
const secret = envSecrets[i];
|
||||
this.url = ReplaceAllOccurrences(this.url, secret.find, secret.replace);
|
||||
this.headers = JSON.parse(ReplaceAllOccurrences(JSON.stringify(this.headers), secret.find, secret.replace));
|
||||
}
|
||||
|
||||
if (!!this.trigger_meta.has_webhook_body && !!this.trigger_meta.webhook_body) {
|
||||
envSecrets = GetRequiredSecrets(this.trigger_meta.webhook_body);
|
||||
for (let i = 0; i < envSecrets.length; i++) {
|
||||
const secret = envSecrets[i];
|
||||
this.trigger_meta.webhook_body = ReplaceAllOccurrences(
|
||||
this.trigger_meta.webhook_body,
|
||||
secret.find,
|
||||
secret.replace
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!!this.trigger_meta.has_webhook_body && !!this.trigger_meta.webhook_body) {
|
||||
envSecrets = GetRequiredSecrets(this.trigger_meta.webhook_body);
|
||||
for (let i = 0; i < envSecrets.length; i++) {
|
||||
const secret = envSecrets[i];
|
||||
this.trigger_meta.webhook_body = ReplaceAllOccurrences(
|
||||
this.trigger_meta.webhook_body,
|
||||
secret.find,
|
||||
secret.replace,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
transformData(data) {
|
||||
if (!!!this.trigger_meta.has_webhook_body) return JSON.stringify(data);
|
||||
if (!!!this.trigger_meta.webhook_body) return JSON.stringify(data);
|
||||
let body = this.trigger_meta.webhook_body;
|
||||
body = ReplaceAllOccurrences(body, "${id}", data.id);
|
||||
body = ReplaceAllOccurrences(body, "${alert_name}", data.alert_name);
|
||||
body = ReplaceAllOccurrences(body, "${severity}", data.severity);
|
||||
body = ReplaceAllOccurrences(body, "${status}", data.status);
|
||||
body = ReplaceAllOccurrences(body, "${source}", data.source);
|
||||
body = ReplaceAllOccurrences(body, "${timestamp}", data.timestamp);
|
||||
body = ReplaceAllOccurrences(body, "${description}", data.description);
|
||||
body = ReplaceAllOccurrences(body, "${metric}", data.details.metric);
|
||||
body = ReplaceAllOccurrences(body, "${current_value}", data.details.current_value);
|
||||
body = ReplaceAllOccurrences(body, "${threshold}", data.details.threshold);
|
||||
body = ReplaceAllOccurrences(body, "${action_text}", data.actions[0].text);
|
||||
body = ReplaceAllOccurrences(body, "${action_url}", data.actions[0].url);
|
||||
return body;
|
||||
}
|
||||
transformData(data) {
|
||||
if (!!!this.trigger_meta.has_webhook_body) return JSON.stringify(data);
|
||||
if (!!!this.trigger_meta.webhook_body) return JSON.stringify(data);
|
||||
let body = this.trigger_meta.webhook_body;
|
||||
body = ReplaceAllOccurrences(body, "${id}", data.id);
|
||||
body = ReplaceAllOccurrences(body, "${alert_name}", data.alert_name);
|
||||
body = ReplaceAllOccurrences(body, "${severity}", data.severity);
|
||||
body = ReplaceAllOccurrences(body, "${status}", data.status);
|
||||
body = ReplaceAllOccurrences(body, "${source}", data.source);
|
||||
body = ReplaceAllOccurrences(body, "${timestamp}", data.timestamp);
|
||||
body = ReplaceAllOccurrences(body, "${description}", data.description);
|
||||
body = ReplaceAllOccurrences(body, "${metric}", data.details.metric);
|
||||
body = ReplaceAllOccurrences(body, "${current_value}", data.details.current_value);
|
||||
body = ReplaceAllOccurrences(body, "${threshold}", data.details.threshold);
|
||||
body = ReplaceAllOccurrences(body, "${action_text}", data.actions[0].text);
|
||||
body = ReplaceAllOccurrences(body, "${action_url}", data.actions[0].url);
|
||||
return body;
|
||||
}
|
||||
|
||||
type() {
|
||||
return "webhook";
|
||||
}
|
||||
type() {
|
||||
return "webhook";
|
||||
}
|
||||
|
||||
async send(data) {
|
||||
try {
|
||||
const response = await fetch(this.url, {
|
||||
method: this.method,
|
||||
headers: this.headers,
|
||||
body: this.transformData(data)
|
||||
});
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error("Error sending webhook", error);
|
||||
return error;
|
||||
}
|
||||
}
|
||||
async send(data) {
|
||||
try {
|
||||
const response = await fetch(this.url, {
|
||||
method: this.method,
|
||||
headers: this.headers,
|
||||
body: this.transformData(data),
|
||||
});
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error("Error sending webhook", error);
|
||||
return error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Webhook;
|
||||
|
||||
@@ -1,325 +1,318 @@
|
||||
// @ts-nocheck
|
||||
import { AllRecordTypes } from "./constants.js";
|
||||
import knexOb from "../../../knexfile.js";
|
||||
|
||||
import dotenv from "dotenv";
|
||||
dotenv.config();
|
||||
const IsValidURL = function (url) {
|
||||
return /^(http|https):\/\/[^ "]+$/.test(url);
|
||||
return /^(http|https):\/\/[^ "]+$/.test(url);
|
||||
};
|
||||
const IsStringURLSafe = function (str) {
|
||||
const regex = /^[A-Za-z0-9\-_.~]+$/;
|
||||
return regex.test(str);
|
||||
const regex = /^[A-Za-z0-9\-_.~]+$/;
|
||||
return regex.test(str);
|
||||
};
|
||||
const IsValidHTTPMethod = function (method) {
|
||||
return /^(GET|POST|PUT|DELETE|HEAD|OPTIONS|PATCH)$/.test(method);
|
||||
return /^(GET|POST|PUT|DELETE|HEAD|OPTIONS|PATCH)$/.test(method);
|
||||
};
|
||||
|
||||
//return given timestamp in UTC
|
||||
const GetNowTimestampUTC = function () {
|
||||
//use js date instead of moment
|
||||
const now = new Date();
|
||||
const timestamp = now.getTime();
|
||||
return Math.floor(timestamp / 1000);
|
||||
//use js date instead of moment
|
||||
const now = new Date();
|
||||
const timestamp = now.getTime();
|
||||
return Math.floor(timestamp / 1000);
|
||||
};
|
||||
//return given timestamp minute start timestamp in UTC
|
||||
const GetMinuteStartTimestampUTC = function (timestamp) {
|
||||
//use js date instead of moment
|
||||
const now = new Date(timestamp * 1000);
|
||||
const minuteStart = new Date(
|
||||
now.getFullYear(),
|
||||
now.getMonth(),
|
||||
now.getDate(),
|
||||
now.getHours(),
|
||||
now.getMinutes(),
|
||||
0,
|
||||
0
|
||||
);
|
||||
const minuteStartTimestamp = minuteStart.getTime();
|
||||
return Math.floor(minuteStartTimestamp / 1000);
|
||||
//use js date instead of moment
|
||||
const now = new Date(timestamp * 1000);
|
||||
const minuteStart = new Date(
|
||||
now.getFullYear(),
|
||||
now.getMonth(),
|
||||
now.getDate(),
|
||||
now.getHours(),
|
||||
now.getMinutes(),
|
||||
0,
|
||||
0,
|
||||
);
|
||||
const minuteStartTimestamp = minuteStart.getTime();
|
||||
return Math.floor(minuteStartTimestamp / 1000);
|
||||
};
|
||||
//return current timestamp minute start timestamp in UTC
|
||||
const GetMinuteStartNowTimestampUTC = function () {
|
||||
//use js date instead of moment
|
||||
const now = new Date();
|
||||
const minuteStart = new Date(
|
||||
now.getFullYear(),
|
||||
now.getMonth(),
|
||||
now.getDate(),
|
||||
now.getHours(),
|
||||
now.getMinutes(),
|
||||
0,
|
||||
0
|
||||
);
|
||||
const minuteStartTimestamp = minuteStart.getTime();
|
||||
return Math.floor(minuteStartTimestamp / 1000);
|
||||
//use js date instead of moment
|
||||
const now = new Date();
|
||||
const minuteStart = new Date(
|
||||
now.getFullYear(),
|
||||
now.getMonth(),
|
||||
now.getDate(),
|
||||
now.getHours(),
|
||||
now.getMinutes(),
|
||||
0,
|
||||
0,
|
||||
);
|
||||
const minuteStartTimestamp = minuteStart.getTime();
|
||||
return Math.floor(minuteStartTimestamp / 1000);
|
||||
};
|
||||
//return given timestamp day start timestamp in UTC
|
||||
const GetDayStartTimestampUTC = function (timestamp) {
|
||||
//use js date instead of moment
|
||||
const now = new Date(timestamp * 1000);
|
||||
const dayStart = new Date(
|
||||
Date.UTC(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0, 0)
|
||||
);
|
||||
const dayStartTimestamp = dayStart.getTime();
|
||||
return Math.floor(dayStartTimestamp / 1000);
|
||||
//use js date instead of moment
|
||||
const now = new Date(timestamp * 1000);
|
||||
const dayStart = new Date(Date.UTC(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0, 0));
|
||||
const dayStartTimestamp = dayStart.getTime();
|
||||
return Math.floor(dayStartTimestamp / 1000);
|
||||
};
|
||||
const GetDayEndTimestampUTC = function (timestamp) {
|
||||
//use js date instead of moment
|
||||
const now = new Date(timestamp * 1000);
|
||||
const dayEnd = new Date(
|
||||
Date.UTC(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59, 999)
|
||||
);
|
||||
const dayEndTimestamp = dayEnd.getTime();
|
||||
return Math.floor(dayEndTimestamp / 1000) + 60;
|
||||
//use js date instead of moment
|
||||
const now = new Date(timestamp * 1000);
|
||||
const dayEnd = new Date(Date.UTC(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59, 999));
|
||||
const dayEndTimestamp = dayEnd.getTime();
|
||||
return Math.floor(dayEndTimestamp / 1000) + 60;
|
||||
};
|
||||
const DurationInMinutes = function (start, end) {
|
||||
return Math.floor((end - start) / 60);
|
||||
return Math.floor((end - start) / 60);
|
||||
};
|
||||
const GetDayStartWithOffset = function (timeStampInSeconds, offsetInMinutes) {
|
||||
const then = new Date(GetMinuteStartTimestampUTC(timeStampInSeconds) * 1000);
|
||||
let dayStartThen = GetDayStartTimestampUTC(then.getTime() / 1000);
|
||||
let dayStartTomorrow = dayStartThen + 24 * 60 * 60;
|
||||
let dayStartYesterday = dayStartThen - 24 * 60 * 60;
|
||||
//have to figure out when to add a day
|
||||
//20-12AM [21-12AM] =21:630 xtm [22-12AM] xtd =22:630 23-12AM
|
||||
const then = new Date(GetMinuteStartTimestampUTC(timeStampInSeconds) * 1000);
|
||||
let dayStartThen = GetDayStartTimestampUTC(then.getTime() / 1000);
|
||||
let dayStartTomorrow = dayStartThen + 24 * 60 * 60;
|
||||
let dayStartYesterday = dayStartThen - 24 * 60 * 60;
|
||||
//have to figure out when to add a day
|
||||
//20-12AM [21-12AM] =21:630 xtm [22-12AM] xtd =22:630 23-12AM
|
||||
|
||||
//if xtm - 330 > 1 day , add a day to xtm - 330
|
||||
//if xtm - 330 > 1 day , add a day to xtm - 330
|
||||
|
||||
if (offsetInMinutes < 0) {
|
||||
//add one day to dayStartThen
|
||||
dayStartThen = dayStartThen + 24 * 60 * 60;
|
||||
}
|
||||
return dayStartThen + offsetInMinutes * 60;
|
||||
if (offsetInMinutes < 0) {
|
||||
//add one day to dayStartThen
|
||||
dayStartThen = dayStartThen + 24 * 60 * 60;
|
||||
}
|
||||
return dayStartThen + offsetInMinutes * 60;
|
||||
};
|
||||
const BeginningOfDay = (options = {}) => {
|
||||
const { date = new Date(), timeZone } = options;
|
||||
const parts = Intl.DateTimeFormat("en-US", {
|
||||
timeZone,
|
||||
hourCycle: "h23",
|
||||
hour: "numeric",
|
||||
minute: "numeric",
|
||||
second: "numeric"
|
||||
}).formatToParts(date);
|
||||
const hour = parseInt(parts.find((i) => i.type === "hour").value);
|
||||
const minute = parseInt(parts.find((i) => i.type === "minute").value);
|
||||
const second = parseInt(parts.find((i) => i.type === "second").value);
|
||||
const dt = new Date(
|
||||
1000 * Math.floor((date - hour * 3600000 - minute * 60000 - second * 1000) / 1000)
|
||||
);
|
||||
return dt.getTime() / 1000;
|
||||
const { date = new Date(), timeZone } = options;
|
||||
const parts = Intl.DateTimeFormat("en-US", {
|
||||
timeZone,
|
||||
hourCycle: "h23",
|
||||
hour: "numeric",
|
||||
minute: "numeric",
|
||||
second: "numeric",
|
||||
}).formatToParts(date);
|
||||
const hour = parseInt(parts.find((i) => i.type === "hour").value);
|
||||
const minute = parseInt(parts.find((i) => i.type === "minute").value);
|
||||
const second = parseInt(parts.find((i) => i.type === "second").value);
|
||||
const dt = new Date(1000 * Math.floor((date - hour * 3600000 - minute * 60000 - second * 1000) / 1000));
|
||||
return dt.getTime() / 1000;
|
||||
};
|
||||
const BeginningOfMinute = (options = {}) => {
|
||||
const { date = new Date(), timeZone } = options;
|
||||
const parts = Intl.DateTimeFormat("en-US", {
|
||||
timeZone,
|
||||
hourCycle: "h23",
|
||||
second: "numeric"
|
||||
}).formatToParts(date);
|
||||
const second = parseInt(parts.find((i) => i.type === "second").value);
|
||||
const dt = new Date(1000 * Math.floor((date - second * 1000) / 1000));
|
||||
return dt.getTime() / 1000;
|
||||
const { date = new Date(), timeZone } = options;
|
||||
const parts = Intl.DateTimeFormat("en-US", {
|
||||
timeZone,
|
||||
hourCycle: "h23",
|
||||
second: "numeric",
|
||||
}).formatToParts(date);
|
||||
const second = parseInt(parts.find((i) => i.type === "second").value);
|
||||
const dt = new Date(1000 * Math.floor((date - second * 1000) / 1000));
|
||||
return dt.getTime() / 1000;
|
||||
};
|
||||
const ValidateIpAddress = function (input) {
|
||||
// Check if input is a valid IPv4 address
|
||||
const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/;
|
||||
if (ipv4Regex.test(input)) {
|
||||
return "IPv4";
|
||||
}
|
||||
// Check if input is a valid IPv4 address
|
||||
const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/;
|
||||
if (ipv4Regex.test(input)) {
|
||||
return "IPv4";
|
||||
}
|
||||
|
||||
// Check if input is a valid IPv6 address
|
||||
const ipv6Regex = /^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$/;
|
||||
if (ipv6Regex.test(input)) {
|
||||
return "IPv6";
|
||||
}
|
||||
// Check if input is a valid IPv6 address
|
||||
const ipv6Regex = /^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$/;
|
||||
if (ipv6Regex.test(input)) {
|
||||
return "IPv6";
|
||||
}
|
||||
|
||||
// Check if input is a valid domain name
|
||||
const domainRegex = /^[a-zA-Z0-9]+([\-\.]{1}[a-zA-Z0-9]+)*\.[a-zA-Z]{2,}$/;
|
||||
if (domainRegex.test(input)) {
|
||||
return "Domain Name";
|
||||
}
|
||||
// Check if input is a valid domain name
|
||||
const domainRegex = /^[a-zA-Z0-9]+([\-\.]{1}[a-zA-Z0-9]+)*\.[a-zA-Z]{2,}$/;
|
||||
if (domainRegex.test(input)) {
|
||||
return "Domain Name";
|
||||
}
|
||||
|
||||
// If none of the above conditions match, the input is invalid
|
||||
return "Invalid";
|
||||
// If none of the above conditions match, the input is invalid
|
||||
return "Invalid";
|
||||
};
|
||||
function checkIfDuplicateExists(arr) {
|
||||
return new Set(arr).size !== arr.length;
|
||||
return new Set(arr).size !== arr.length;
|
||||
}
|
||||
function GetWordsStartingWithDollar(text) {
|
||||
const regex = /\$\w+/g;
|
||||
const wordsArray = text.match(regex);
|
||||
return wordsArray || [];
|
||||
const regex = /\$\w+/g;
|
||||
const wordsArray = text.match(regex);
|
||||
return wordsArray || [];
|
||||
}
|
||||
const StatusObj = {
|
||||
UP: "api-up",
|
||||
DEGRADED: "api-degraded",
|
||||
DOWN: "api-down",
|
||||
NO_DATA: "api-nodata"
|
||||
UP: "api-up",
|
||||
DEGRADED: "api-degraded",
|
||||
DOWN: "api-down",
|
||||
NO_DATA: "api-nodata",
|
||||
};
|
||||
// @ts-ignore
|
||||
const ParseUptime = function (up, all) {
|
||||
if (all === 0) return String("-");
|
||||
if (up == 0) return String("0");
|
||||
if (up == all) {
|
||||
return String(((up / all) * parseFloat(100)).toFixed(0));
|
||||
}
|
||||
//return 50% as 50% and not 50.0000%
|
||||
if (((up / all) * 100) % 10 == 0) {
|
||||
return String(((up / all) * parseFloat(100)).toFixed(0));
|
||||
}
|
||||
return String(((up / all) * parseFloat(100)).toFixed(4));
|
||||
if (all === 0) return String("-");
|
||||
if (up == 0) return String("0");
|
||||
if (up == all) {
|
||||
return String(((up / all) * parseFloat(100)).toFixed(0));
|
||||
}
|
||||
//return 50% as 50% and not 50.0000%
|
||||
if (((up / all) * 100) % 10 == 0) {
|
||||
return String(((up / all) * parseFloat(100)).toFixed(0));
|
||||
}
|
||||
return String(((up / all) * parseFloat(100)).toFixed(4));
|
||||
};
|
||||
const ParsePercentage = function (n) {
|
||||
if (isNaN(n)) return "-";
|
||||
if (n == 0) {
|
||||
return "0";
|
||||
}
|
||||
if (n == 100) {
|
||||
return "100";
|
||||
}
|
||||
return n.toFixed(4);
|
||||
if (isNaN(n)) return "-";
|
||||
if (n == 0) {
|
||||
return "0";
|
||||
}
|
||||
if (n == 100) {
|
||||
return "100";
|
||||
}
|
||||
return n.toFixed(4);
|
||||
};
|
||||
|
||||
//valid domain name
|
||||
function IsValidHost(domain) {
|
||||
const regex = /^[a-zA-Z0-9]+([\-\.]{1}[a-zA-Z0-9]+)*\.[a-zA-Z]{2,}$/;
|
||||
return regex.test(domain);
|
||||
const regex = /^[a-zA-Z0-9]+([\-\.]{1}[a-zA-Z0-9]+)*\.[a-zA-Z]{2,}$/;
|
||||
return regex.test(domain);
|
||||
}
|
||||
|
||||
//valid nameserver
|
||||
function IsValidNameServer(nameServer) {
|
||||
//8.8.8.8 example
|
||||
const regex = /^([0-9]{1,3}\.){3}[0-9]{1,3}$/;
|
||||
return regex.test(nameServer);
|
||||
//8.8.8.8 example
|
||||
const regex = /^([0-9]{1,3}\.){3}[0-9]{1,3}$/;
|
||||
return regex.test(nameServer);
|
||||
}
|
||||
|
||||
//valid dns record type
|
||||
function IsValidRecordType(recordType) {
|
||||
return AllRecordTypes.hasOwnProperty(recordType);
|
||||
return AllRecordTypes.hasOwnProperty(recordType);
|
||||
}
|
||||
function ReplaceAllOccurrences(originalString, searchString, replacement) {
|
||||
const regex = new RegExp(`\\${searchString}`, "g");
|
||||
const replacedString = originalString.replace(regex, replacement);
|
||||
return replacedString;
|
||||
const regex = new RegExp(`\\${searchString}`, "g");
|
||||
const replacedString = originalString.replace(regex, replacement);
|
||||
return replacedString;
|
||||
}
|
||||
|
||||
function GetRequiredSecrets(str) {
|
||||
let envSecrets = [];
|
||||
const requiredSecrets = GetWordsStartingWithDollar(str).map((x) => x.substr(1));
|
||||
for (const [key, value] of Object.entries(process.env)) {
|
||||
if (requiredSecrets.indexOf(key) !== -1) {
|
||||
envSecrets.push({
|
||||
find: `$${key}`,
|
||||
replace: value
|
||||
});
|
||||
}
|
||||
}
|
||||
return envSecrets;
|
||||
let envSecrets = [];
|
||||
const requiredSecrets = GetWordsStartingWithDollar(str).map((x) => x.substr(1));
|
||||
for (const [key, value] of Object.entries(process.env)) {
|
||||
if (requiredSecrets.indexOf(key) !== -1) {
|
||||
envSecrets.push({
|
||||
find: `$${key}`,
|
||||
replace: value,
|
||||
});
|
||||
}
|
||||
}
|
||||
return envSecrets;
|
||||
}
|
||||
|
||||
function ValidateMonitorAlerts(alerts) {
|
||||
if (!alerts) {
|
||||
console.log("Alerts object is not provided.");
|
||||
return false;
|
||||
}
|
||||
// if down degraded not present return false
|
||||
if (!alerts.hasOwnProperty("DOWN") && !alerts.hasOwnProperty("DEGRADED")) {
|
||||
console.log("Alerts object does not have DOWN or DEGRADED properties.");
|
||||
return false;
|
||||
}
|
||||
let statues = Object.keys(alerts);
|
||||
//can be either DOWN or DEGRADED
|
||||
let validKeys = ["DOWN", "DEGRADED"];
|
||||
for (let i = 0; i < statues.length; i++) {
|
||||
const keyName = statues[i];
|
||||
if (!validKeys.includes(keyName)) {
|
||||
console.log(`Invalid key found in alerts: ${keyName}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (const key in alerts) {
|
||||
if (Object.prototype.hasOwnProperty.call(alerts, key)) {
|
||||
const element = alerts[key];
|
||||
const triggers = element.triggers;
|
||||
//if triggers not present return false
|
||||
if (!element.hasOwnProperty("triggers")) {
|
||||
console.log(`Triggers not present for key: ${key}`);
|
||||
return false;
|
||||
}
|
||||
//if length of triggers is 0 return false
|
||||
if (triggers.length === 0) {
|
||||
console.log(`Triggers length is 0 for key: ${key}`);
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
!element.hasOwnProperty("failureThreshold") ||
|
||||
!element.hasOwnProperty("successThreshold")
|
||||
) {
|
||||
console.log(`Thresholds not present for key: ${key}`);
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
element.hasOwnProperty("failureThreshold") &&
|
||||
!Number.isInteger(element.failureThreshold)
|
||||
) {
|
||||
console.log(`Failure threshold is not an integer for key: ${key}`);
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
element.hasOwnProperty("successThreshold") &&
|
||||
!Number.isInteger(element.successThreshold)
|
||||
) {
|
||||
console.log(`Success threshold is not an integer for key: ${key}`);
|
||||
return false;
|
||||
}
|
||||
if (element.hasOwnProperty("failureThreshold") && element.failureThreshold <= 0) {
|
||||
console.log(`Failure threshold is less than or equal to 0 for key: ${key}`);
|
||||
return false;
|
||||
}
|
||||
if (element.hasOwnProperty("successThreshold") && element.successThreshold <= 0) {
|
||||
console.log(`Success threshold is less than or equal to 0 for key: ${key}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
if (!alerts) {
|
||||
console.log("Alerts object is not provided.");
|
||||
return false;
|
||||
}
|
||||
// if down degraded not present return false
|
||||
if (!alerts.hasOwnProperty("DOWN") && !alerts.hasOwnProperty("DEGRADED")) {
|
||||
console.log("Alerts object does not have DOWN or DEGRADED properties.");
|
||||
return false;
|
||||
}
|
||||
let statues = Object.keys(alerts);
|
||||
//can be either DOWN or DEGRADED
|
||||
let validKeys = ["DOWN", "DEGRADED"];
|
||||
for (let i = 0; i < statues.length; i++) {
|
||||
const keyName = statues[i];
|
||||
if (!validKeys.includes(keyName)) {
|
||||
console.log(`Invalid key found in alerts: ${keyName}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (const key in alerts) {
|
||||
if (Object.prototype.hasOwnProperty.call(alerts, key)) {
|
||||
const element = alerts[key];
|
||||
const triggers = element.triggers;
|
||||
//if triggers not present return false
|
||||
if (!element.hasOwnProperty("triggers")) {
|
||||
console.log(`Triggers not present for key: ${key}`);
|
||||
return false;
|
||||
}
|
||||
//if length of triggers is 0 return false
|
||||
if (triggers.length === 0) {
|
||||
console.log(`Triggers length is 0 for key: ${key}`);
|
||||
return false;
|
||||
}
|
||||
if (!element.hasOwnProperty("failureThreshold") || !element.hasOwnProperty("successThreshold")) {
|
||||
console.log(`Thresholds not present for key: ${key}`);
|
||||
return false;
|
||||
}
|
||||
if (element.hasOwnProperty("failureThreshold") && !Number.isInteger(element.failureThreshold)) {
|
||||
console.log(`Failure threshold is not an integer for key: ${key}`);
|
||||
return false;
|
||||
}
|
||||
if (element.hasOwnProperty("successThreshold") && !Number.isInteger(element.successThreshold)) {
|
||||
console.log(`Success threshold is not an integer for key: ${key}`);
|
||||
return false;
|
||||
}
|
||||
if (element.hasOwnProperty("failureThreshold") && element.failureThreshold <= 0) {
|
||||
console.log(`Failure threshold is less than or equal to 0 for key: ${key}`);
|
||||
return false;
|
||||
}
|
||||
if (element.hasOwnProperty("successThreshold") && element.successThreshold <= 0) {
|
||||
console.log(`Success threshold is less than or equal to 0 for key: ${key}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
function GenerateRandomColor() {
|
||||
var randomColor = Math.floor(Math.random() * 16777215).toString(16);
|
||||
return randomColor;
|
||||
var randomColor = Math.floor(Math.random() * 16777215).toString(16);
|
||||
return randomColor;
|
||||
}
|
||||
|
||||
//wait for x ms promise
|
||||
function Wait(ms) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
function MaskString(str) {
|
||||
return "*".repeat(str.length - 4) + str.slice(-4);
|
||||
return "*".repeat(str.length - 4) + str.slice(-4);
|
||||
}
|
||||
|
||||
function GetDbType() {
|
||||
//sqlite, postgresql, mysql
|
||||
return knexOb.databaseType;
|
||||
}
|
||||
|
||||
export {
|
||||
IsValidURL,
|
||||
IsValidHTTPMethod,
|
||||
GetMinuteStartTimestampUTC,
|
||||
GetNowTimestampUTC,
|
||||
GetDayStartTimestampUTC,
|
||||
GetMinuteStartNowTimestampUTC,
|
||||
DurationInMinutes,
|
||||
GetDayStartWithOffset,
|
||||
BeginningOfDay,
|
||||
IsStringURLSafe,
|
||||
ValidateIpAddress,
|
||||
checkIfDuplicateExists,
|
||||
GetWordsStartingWithDollar,
|
||||
StatusObj,
|
||||
ParseUptime,
|
||||
ParsePercentage,
|
||||
IsValidHost,
|
||||
IsValidRecordType,
|
||||
IsValidNameServer,
|
||||
ReplaceAllOccurrences,
|
||||
GetRequiredSecrets,
|
||||
ValidateMonitorAlerts,
|
||||
GenerateRandomColor,
|
||||
BeginningOfMinute,
|
||||
Wait,
|
||||
MaskString
|
||||
IsValidURL,
|
||||
IsValidHTTPMethod,
|
||||
GetMinuteStartTimestampUTC,
|
||||
GetNowTimestampUTC,
|
||||
GetDayStartTimestampUTC,
|
||||
GetMinuteStartNowTimestampUTC,
|
||||
DurationInMinutes,
|
||||
GetDayStartWithOffset,
|
||||
BeginningOfDay,
|
||||
IsStringURLSafe,
|
||||
ValidateIpAddress,
|
||||
checkIfDuplicateExists,
|
||||
GetWordsStartingWithDollar,
|
||||
StatusObj,
|
||||
ParseUptime,
|
||||
ParsePercentage,
|
||||
IsValidHost,
|
||||
IsValidRecordType,
|
||||
IsValidNameServer,
|
||||
ReplaceAllOccurrences,
|
||||
GetRequiredSecrets,
|
||||
ValidateMonitorAlerts,
|
||||
GenerateRandomColor,
|
||||
BeginningOfMinute,
|
||||
Wait,
|
||||
MaskString,
|
||||
GetDbType,
|
||||
};
|
||||
|
||||
@@ -110,7 +110,7 @@
|
||||
<!-- Document Icon - Replace with your own logo -->
|
||||
<img src="https://kener.ing/logo.png" class="h-8 w-8" alt="" />
|
||||
<span class="text-xl font-medium">Kener Documentation</span>
|
||||
<span class="me-2 rounded border px-2.5 py-0.5 text-xs font-medium"> 3.2.1 </span>
|
||||
<span class="me-2 rounded border px-2.5 py-0.5 text-xs font-medium"> 3.2.2 </span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -633,7 +633,6 @@
|
||||
variant="ghost"
|
||||
class="h-5 w-5 p-1"
|
||||
size="icon"
|
||||
disabled={incident.isAutoCreated}
|
||||
on:click={(e) => {
|
||||
showEditModal(incident);
|
||||
}}
|
||||
|
||||
@@ -2,67 +2,67 @@ import { fontFamily } from "tailwindcss/defaultTheme";
|
||||
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
const config = {
|
||||
darkMode: ["class"],
|
||||
content: ["./src/**/*.{html,js,svelte,ts}"],
|
||||
safelist: ["dark"],
|
||||
plugins: [
|
||||
require("@tailwindcss/typography")
|
||||
// ...
|
||||
],
|
||||
theme: {
|
||||
container: {
|
||||
center: true,
|
||||
padding: "2rem",
|
||||
screens: {
|
||||
"2xl": "1400px"
|
||||
}
|
||||
},
|
||||
extend: {
|
||||
colors: {
|
||||
border: "hsl(var(--border) / <alpha-value>)",
|
||||
input: "hsl(var(--input) / <alpha-value>)",
|
||||
ring: "hsl(var(--ring) / <alpha-value>)",
|
||||
background: "hsl(var(--background) / <alpha-value>)",
|
||||
foreground: "hsl(var(--foreground) / <alpha-value>)",
|
||||
primary: {
|
||||
DEFAULT: "hsl(var(--primary) / <alpha-value>)",
|
||||
foreground: "hsl(var(--primary-foreground) / <alpha-value>)"
|
||||
},
|
||||
secondary: {
|
||||
DEFAULT: "hsl(var(--secondary) / <alpha-value>)",
|
||||
foreground: "hsl(var(--secondary-foreground) / <alpha-value>)"
|
||||
},
|
||||
destructive: {
|
||||
DEFAULT: "hsl(var(--destructive) / <alpha-value>)",
|
||||
foreground: "hsl(var(--destructive-foreground) / <alpha-value>)"
|
||||
},
|
||||
muted: {
|
||||
DEFAULT: "hsl(var(--muted) / <alpha-value>)",
|
||||
foreground: "hsl(var(--muted-foreground) / <alpha-value>)"
|
||||
},
|
||||
accent: {
|
||||
DEFAULT: "hsl(var(--accent) / <alpha-value>)",
|
||||
foreground: "hsl(var(--accent-foreground) / <alpha-value>)"
|
||||
},
|
||||
popover: {
|
||||
DEFAULT: "hsl(var(--popover) / <alpha-value>)",
|
||||
foreground: "hsl(var(--popover-foreground) / <alpha-value>)"
|
||||
},
|
||||
card: {
|
||||
DEFAULT: "hsl(var(--card) / <alpha-value>)",
|
||||
foreground: "hsl(var(--card-foreground) / <alpha-value>)"
|
||||
}
|
||||
},
|
||||
borderRadius: {
|
||||
lg: "var(--radius)",
|
||||
md: "calc(var(--radius) - 2px)",
|
||||
sm: "calc(var(--radius) - 4px)"
|
||||
},
|
||||
fontFamily: {
|
||||
sans: [...fontFamily.sans]
|
||||
}
|
||||
}
|
||||
}
|
||||
darkMode: ["class"],
|
||||
content: ["./src/**/*.{html,js,svelte,ts}"],
|
||||
safelist: ["dark"],
|
||||
plugins: [
|
||||
require("@tailwindcss/typography"),
|
||||
// ...
|
||||
],
|
||||
theme: {
|
||||
container: {
|
||||
center: true,
|
||||
padding: "2rem",
|
||||
screens: {
|
||||
"2xl": "1400px",
|
||||
},
|
||||
},
|
||||
extend: {
|
||||
colors: {
|
||||
border: "hsl(var(--border) / <alpha-value>)",
|
||||
input: "hsl(var(--input) / <alpha-value>)",
|
||||
ring: "hsl(var(--ring) / <alpha-value>)",
|
||||
background: "hsl(var(--background) / <alpha-value>)",
|
||||
foreground: "hsl(var(--foreground) / <alpha-value>)",
|
||||
primary: {
|
||||
DEFAULT: "hsl(var(--primary) / <alpha-value>)",
|
||||
foreground: "hsl(var(--primary-foreground) / <alpha-value>)",
|
||||
},
|
||||
secondary: {
|
||||
DEFAULT: "hsl(var(--secondary) / <alpha-value>)",
|
||||
foreground: "hsl(var(--secondary-foreground) / <alpha-value>)",
|
||||
},
|
||||
destructive: {
|
||||
DEFAULT: "hsl(var(--destructive) / <alpha-value>)",
|
||||
foreground: "hsl(var(--destructive-foreground) / <alpha-value>)",
|
||||
},
|
||||
muted: {
|
||||
DEFAULT: "hsl(var(--muted) / <alpha-value>)",
|
||||
foreground: "hsl(var(--muted-foreground) / <alpha-value>)",
|
||||
},
|
||||
accent: {
|
||||
DEFAULT: "hsl(var(--accent) / <alpha-value>)",
|
||||
foreground: "hsl(var(--accent-foreground) / <alpha-value>)",
|
||||
},
|
||||
popover: {
|
||||
DEFAULT: "hsl(var(--popover) / <alpha-value>)",
|
||||
foreground: "hsl(var(--popover-foreground) / <alpha-value>)",
|
||||
},
|
||||
card: {
|
||||
DEFAULT: "hsl(var(--card) / <alpha-value>)",
|
||||
foreground: "hsl(var(--card-foreground) / <alpha-value>)",
|
||||
},
|
||||
},
|
||||
borderRadius: {
|
||||
lg: "var(--radius)",
|
||||
md: "calc(var(--radius) - 2px)",
|
||||
sm: "calc(var(--radius) - 4px)",
|
||||
},
|
||||
fontFamily: {
|
||||
sans: [...fontFamily.sans],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
||||
Reference in New Issue
Block a user