feat: pre release 3.0.0

This commit is contained in:
Raj Nandan Sharma
2024-12-27 16:58:51 +05:30
parent 0735f959ef
commit 8b9f576b30
21 changed files with 519 additions and 72 deletions

View File

@@ -99,8 +99,8 @@ section {
}
/*Needed overlay content on top of dotted bg*/
.blurry-bg {
background-color: var(--background-kener-rgba);
box-shadow: 0 0 64px 64px var(--background-kener-rgba);
/* background-color: var(--background-kener-rgba);
box-shadow: 0 0 64px 64px var(--background-kener-rgba); */
}
:root {

View File

@@ -107,7 +107,7 @@
/>
{/if}
<div class="px-8">
<div class="">
<div class="scroll-m-20 text-xl font-medium tracking-tight">
{#if variant.includes("monitor")}
{monitor.name} -
@@ -138,7 +138,7 @@
</div>
{/if}
</Card.Title>
<Card.Description class="-mt-4 px-8 text-xs ">
<Card.Description class="-mt-4 text-xs ">
{moment(incidentCreatedAt * 1000).format("MMMM Do YYYY, h:mm:ss a")}
<p class="mt-0 flex gap-2 leading-8">
@@ -167,7 +167,7 @@
</Card.Description>
</Card.Header>
{#if (variant.includes("body") || variant.includes("comments")) && state == "open"}
<Card.Content class="px-14">
<Card.Content class="px-7">
{#if variant.includes("body")}
<div
class="prose prose-stone max-w-none text-sm dark:prose-invert prose-code:rounded prose-code:px-[0.3rem] prose-code:py-[0.2rem] prose-code:font-mono prose-code:text-sm"

View File

@@ -0,0 +1,266 @@
<script>
import { onMount } from "svelte";
import { base } from "$app/paths";
import moment from "moment";
import { Button } from "$lib/components/ui/button";
import { Plus, X, Settings, Bell, Loader, Copy, Check } from "lucide-svelte";
import { Input } from "$lib/components/ui/input";
import { Label } from "$lib/components/ui/label";
let apiKeys = [];
let loaderLoadingAll = false;
let loaderCreateNew = false;
let newAPIKeyName = "";
let newKeyResp = {};
let showCreateModal = false;
async function loadAPIKeys() {
loaderLoadingAll = true;
try {
let apiResp = await fetch(base + "/manage/api/", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
action: "getAPIKeys",
data: {}
})
});
let resp = await apiResp.json();
apiKeys = resp;
} catch (error) {
alert("Error: " + error);
} finally {
loaderLoadingAll = false;
}
}
async function createNew() {
newKeyResp = {};
loaderCreateNew = true;
try {
let apiResp = await fetch(base + "/manage/api/", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
action: "createNewApiKey",
data: {
name: newAPIKeyName
}
})
});
newKeyResp = await apiResp.json();
loadAPIKeys();
showCreateModal = false;
} catch (error) {
alert("Error: " + error);
} finally {
loaderCreateNew = false;
}
}
onMount(() => {
loadAPIKeys();
});
function copyKey() {
navigator.clipboard.writeText(newKeyResp.apiKey);
}
function updateStatus(apiKey) {
apiKey.status = apiKey.status == "ACTIVE" ? "INACTIVE" : "ACTIVE";
fetch(base + "/manage/api/", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
action: "updateApiKeyStatus",
data: {
id: apiKey.id,
status: apiKey.status
}
})
});
}
</script>
<div class="mt-4">
<div class="mb-4 flex justify-between">
<div>
{#if loaderLoadingAll}
<Loader class="mt-6 h-4 w-4 animate-spin" />
{/if}
</div>
<div>
<Button
on:click={(e) => {
showCreateModal = true;
}}
>
<Plus class="mr-2 h-4 w-4" /> Create New API Key
</Button>
</div>
</div>
</div>
{#if !!newKeyResp && !!newKeyResp.apiKey}
<div class="my-4 rounded-lg border border-green-700 bg-green-800 bg-opacity-20 p-4">
<p class="font-medium">
<picture class="mr-1 inline-block">
<source
srcset="https://fonts.gstatic.com/s/e/notoemoji/latest/1f389/512.webp"
type="image/webp"
/>
<img
src="https://fonts.gstatic.com/s/e/notoemoji/latest/1f389/512.gif"
alt="🎉"
width="24"
height="24"
/>
</picture>
API Key Created
</p>
<p
class="relative my-2 rounded-sm border bg-card px-4 py-2 pr-8 font-mono text-sm font-medium"
>
{newKeyResp.apiKey}
<Button
size="icon"
variant="ghost"
class="copybtn absolute right-2 top-2 h-5 w-5 p-1"
on:click={copyKey}
>
<Check class="check-btn absolute left-0 top-0 h-4 w-4 text-green-500" />
<Copy class="copy-btn absolute left-0 top-0 h-4 w-4 " />
</Button>
</p>
<p class="text-xs text-muted-foreground">
Your new API key has been created. It will be not shown again, so make sure to save it.
</p>
</div>
{/if}
<div class="flex flex-col">
<div class="-m-1.5 overflow-x-auto">
<div class="inline-block min-w-full p-1.5 align-middle">
<div class="overflow-hidden rounded-lg border dark:border-neutral-700">
<table class="min-w-full divide-y divide-gray-200 dark:divide-neutral-700">
<thead>
<tr>
<th
scope="col"
class="px-6 py-3 text-start text-xs font-medium uppercase text-gray-500 dark:text-neutral-500"
>Name</th
>
<th
scope="col"
class="px-6 py-3 text-start text-xs font-medium uppercase text-gray-500 dark:text-neutral-500"
>Key</th
>
<th
scope="col"
class="px-6 py-3 text-start text-xs font-medium uppercase text-gray-500 dark:text-neutral-500"
>Created At</th
>
<th
scope="col"
class="px-6 py-3 text-start text-xs font-medium uppercase text-gray-500 dark:text-neutral-500"
></th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 dark:divide-neutral-700">
{#each apiKeys as apiKey}
<tr>
<td
class="whitespace-nowrap px-6 py-4 text-sm font-medium text-gray-800 dark:text-neutral-200"
>
{apiKey.name}
</td>
<td
class="whitespace-nowrap px-6 py-4 text-xs font-semibold text-gray-800 dark:text-neutral-200"
>
{apiKey.maskedKey.slice(-32)}
</td>
<td
class="whitespace-nowrap px-6 py-4 text-sm text-gray-800 dark:text-neutral-200"
>
{moment(apiKey.createdAt).format("YYYY-MM-DD HH:mm:ss")}
</td>
<td
class="whitespace-nowrap px-6 py-4 text-xs font-semibold text-gray-800 dark:text-neutral-200"
>
<label class="inline-flex cursor-pointer items-center">
<input
type="checkbox"
value=""
class="peer sr-only"
checked={apiKey.status == "ACTIVE"}
on:change={() => {
updateStatus(apiKey);
}}
/>
<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"
></div>
</label>
</td>
</tr>
{/each}
</tbody>
</table>
</div>
</div>
</div>
</div>
{#if showCreateModal}
<div
class="moldal-container fixed left-0 top-0 z-30 h-screen w-full bg-card bg-opacity-30 backdrop-blur-sm"
>
<div
class="absolute left-1/2 top-1/2 h-fit w-full max-w-xl -translate-x-1/2 -translate-y-1/2 rounded-md border bg-background shadow-lg backdrop-blur-lg"
>
<Button
variant="ghost"
on:click={() => {
showCreateModal = false;
}}
class="absolute right-2 top-2 z-40 h-6 w-6 rounded-full border bg-background p-1"
>
<X class="h-4 w-4 text-muted-foreground" />
</Button>
<div class="content px-4 py-4">
<h2 class="text-lg font-semibold">Create a new API Key</h2>
<p class="text-xs text-muted-foreground">
API keys are used to authenticate your requests to the API. They are unique to
your account and should be kept secret.
</p>
<hr class="my-4" />
<form on:submit|preventDefault={createNew}>
<div class="flex flex-col gap-4">
<div>
<Label for="newAPIKeyName">Name</Label>
<Input
bind:value={newAPIKeyName}
class="mt-2"
type="text"
id="newAPIKeyName"
placeholder="eg. My API Key"
/>
</div>
</div>
<div class="mt-4 flex justify-end">
<Button variant="secondary" type="submit" disabled={loaderCreateNew}>
Create
{#if loaderCreateNew}
<Loader class="ml-2 inline h-4 w-4 animate-spin" />
{/if}
</Button>
</div>
</form>
</div>
</div>
</div>
{/if}

View File

@@ -10,6 +10,8 @@
import * as Select from "$lib/components/ui/select";
export let categories = [];
export let colorDown = "#777";
export let colorDegraded = "#777";
let monitors = [];
let status = "ACTIVE";
let showAddMonitor = false;
@@ -313,8 +315,11 @@
<hr class="my-4" />
{#each Object.entries(monitorTriggers) as [key, data]}
<div class="flex justify-between">
<h3 class="font-semibold">
{data.triggerType}
<h3
class="font-semibold"
style="color:{data.triggerType == 'DOWN' ? colorDown : colorDegraded};"
>
If Monitor {data.triggerType}
</h3>
<div>
<label class="inline-flex cursor-pointer items-center">
@@ -409,7 +414,9 @@
/>
</div>
{#each triggers as trigger}
<div class="col-span-1 mt-2">
<div
class="col-span-1 mt-2 overflow-hidden overflow-ellipsis whitespace-nowrap"
>
<label class="cursor-pointer">
<input
type="checkbox"

View File

@@ -393,7 +393,7 @@
<div class="rounded-md border bg-card p-2 text-xs">
<p class="text-sm font-semibold">Email Trigger</p>
<p class="text-xs">
Kener used <a
Kener uses <a
href="https://resend.com/"
class="text-blue-500"
target="_blank">resend</a

View File

@@ -67,7 +67,7 @@ async function createGHIncident(monitor, alert, commonData) {
payload.body = description;
let { title, body, githubLabels, error } = ParseIncidentPayload(payload);
let { title, body, githubLabels, error } = await ParseIncidentPayload(payload);
if (error) {
return;
}
@@ -75,7 +75,7 @@ async function createGHIncident(monitor, alert, commonData) {
githubLabels.push("auto");
let resp = await CreateIssue(title, body, githubLabels);
return GHIssueToKenerIncident(resp);
return await GHIssueToKenerIncident(resp);
}
async function closeGHIncident(alert) {
@@ -101,7 +101,7 @@ async function closeGHIncident(alert) {
return;
}
await CloseIssue(incidentNumber);
return GHIssueToKenerIncident(resp);
return await GHIssueToKenerIncident(resp);
}
//add comment to incident
@@ -117,7 +117,8 @@ function createClosureComment(alert, commonJSON) {
return comment;
}
async function alerting(monitor) {
async function alerting(m) {
let monitor = await db.getMonitorByTag(m.tag);
let siteData = await GetAllSiteData();
const githubData = await GetGithubData();
const triggers = await GetAllTriggers({
@@ -157,6 +158,10 @@ async function alerting(monitor) {
);
continue;
}
if (trigger.triggerStatus !== "ACTIVE") {
console.error(`Triggers ${triggerID} is not active`);
continue;
}
const notificationClient = new notification(trigger, siteData, monitor);
allMonitorClients.push(notificationClient);
}

View File

@@ -14,6 +14,7 @@ import {
import db from "../db/db.js";
import bcrypt from "bcrypt";
import jwt from "jsonwebtoken";
import crypto from "crypto";
const saltRounds = 10;
const DUMMY_SECRET = "DUMMY_SECRET";
@@ -254,7 +255,9 @@ export const VerifyPassword = async (plainTextPassword, hashedPassword) => {
export const GenerateToken = async (data) => {
try {
const token = jwt.sign(data, process.env.JWT_SECRET || DUMMY_SECRET, { expiresIn: "1y" });
const token = jwt.sign(data, process.env.KENER_SECRET_KEY || DUMMY_SECRET, {
expiresIn: "1y"
});
return token;
} catch (err) {
console.error("Error generating token:", err);
@@ -264,11 +267,11 @@ export const GenerateToken = async (data) => {
export const VerifyToken = async (token) => {
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET || DUMMY_SECRET);
const decoded = jwt.verify(token, process.env.KENER_SECRET_KEY || DUMMY_SECRET);
return decoded; // Returns the decoded payload if the token is valid
} catch (err) {
console.error("Error verifying token:", err);
throw new Error("Invalid or expired token");
return undefined; // Returns null if the token is invalid
}
};
@@ -278,3 +281,58 @@ export const GetAllAlertsPaginated = async (data) => {
total: await db.getMonitorAlertsCount()
};
};
function generateApiKey() {
const prefix = "kener_";
const randomKey = crypto.randomBytes(32).toString("hex"); // 64-character hexadecimal string
return prefix + randomKey;
}
function createHash(apiKey) {
return crypto
.createHmac("sha256", process.env.KENER_SECRET_KEY || DUMMY_SECRET)
.update(apiKey)
.digest("hex");
}
export const MaskString = (str) => {
const len = str.length;
const mask = "*";
const masked = mask.repeat(len - 4) + str.substring(len - 4);
return masked;
};
export const CreateNewAPIKey = async (data) => {
//generate a new key
const apiKey = generateApiKey();
const hashedKey = await createHash(apiKey);
//insert into db
await db.createNewApiKey({
name: data.name,
hashedKey: hashedKey,
maskedKey: MaskString(apiKey)
});
return {
apiKey: apiKey,
name: data.name
};
};
export const GetAllAPIKeys = async () => {
return await db.getAllApiKeys();
};
//update status of api key
export const UpdateApiKeyStatus = async (data) => {
return await db.updateApiKeyStatus(data);
};
export const VerifyAPIKey = async (apiKey) => {
const hashedKey = createHash(apiKey);
// Check if the hash exists in the database
const record = await db.getApiKeyByHashedKey(hashedKey);
if (!!record) {
return record.status == "ACTIVE";
} // Adjust this for your DB query
return false;
};

View File

@@ -98,6 +98,17 @@ class Sqlite {
updatedAt DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- create table ApiKeys
CREATE TABLE IF NOT EXISTS ApiKeys (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
hashedKey TEXT NOT NULL UNIQUE,
maskedKey TEXT NOT NULL,
status TEXT DEFAULT 'ACTIVE',
createdAt DATETIME DEFAULT CURRENT_TIMESTAMP,
updatedAt DATETIME DEFAULT CURRENT_TIMESTAMP
);
`);
}
@@ -455,6 +466,15 @@ class Sqlite {
return stmt.all({ status: data.status });
}
//get monitor by tag
async getMonitorByTag(tag) {
let stmt = this.db.prepare(`
SELECT * FROM Monitors
WHERE tag = @tag;
`);
return stmt.get({ tag });
}
//insert alert
async createNewTrigger(data) {
let stmt = this.db.prepare(`
@@ -524,6 +544,42 @@ class Sqlite {
return stmt.run(data);
}
//new api key
async createNewApiKey(data) {
let stmt = this.db.prepare(`
INSERT INTO ApiKeys (name, hashedKey, maskedKey)
VALUES (@name, @hashedKey, @maskedKey);
`);
return stmt.run(data);
}
//update status of api key
async updateApiKeyStatus(data) {
let stmt = this.db.prepare(`
UPDATE ApiKeys
SET status = @status, updatedAt = CURRENT_TIMESTAMP
WHERE id = @id;
`);
return stmt.run(data);
}
//get key by hashedKey
async getApiKeyByHashedKey(hashedKey) {
let stmt = this.db.prepare(`
SELECT * FROM ApiKeys
WHERE hashedKey = @hashedKey;
`);
return stmt.get({ hashedKey });
}
//get all api keys
async getAllApiKeys() {
let stmt = this.db.prepare(`
SELECT * FROM ApiKeys order by id desc;
`);
return stmt.all();
}
//close
close() {
this.db.close();

View File

@@ -5,9 +5,8 @@ class Discord {
method;
siteData;
monitorData;
envSecrets;
constructor(url, siteData, monitorData, envSecrets) {
constructor(url, siteData, monitorData) {
const kenerHeader = {
"Content-Type": "application/json",
"User-Agent": "Kener"
@@ -18,7 +17,6 @@ class Discord {
this.method = "POST";
this.siteData = siteData;
this.monitorData = monitorData;
this.envSecrets = envSecrets;
}
transformData(data) {
@@ -36,7 +34,6 @@ class Discord {
}
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: [
{
@@ -67,8 +64,7 @@ class Discord {
}
],
footer: {
text: "Kener",
icon_url: logo
text: "Kener"
},
timestamp: data.timestamp
}

View File

@@ -1,6 +1,4 @@
// @ts-nocheck
import { monitorsStore } from "./stores/monitors.js";
import { get } from "svelte/store";
import {
GetMinuteStartNowTimestampUTC,
GetNowTimestampUTC,
@@ -14,32 +12,37 @@ const API_IP = process.env.API_IP;
const API_IP_REGEX = process.env.API_IP_REGEX;
import db from "./db/db.js";
const GetAllTags = function () {
import { GetMonitors, VerifyAPIKey } from "./controllers/controller.js";
const GetAllTags = async function () {
let tags = [];
let monitors = [];
try {
monitors = get(monitorsStore);
monitors = await GetMonitors({
status: "ACTIVE"
});
tags = monitors.map((monitor) => monitor.tag);
} catch (err) {
return [];
}
return tags;
};
const CheckIfValidTag = function (tag) {
const CheckIfValidTag = async function (tag) {
let tags = [];
let monitors = [];
try {
monitors = get(monitorsStore);
tags = monitors.map((monitor) => monitor.tag);
if (tags.indexOf(tag) == -1) {
let monitor = await db.getMonitorByTag(tag);
if (!!!monitor) {
throw new Error("not a valid tag");
}
if (monitor.status != "ACTIVE") {
throw new Error("monitor is not active");
}
} catch (err) {
return false;
}
return true;
};
const auth = function (request) {
const auth = async function (request) {
const authHeader = request.headers.get("authorization");
const authToken = authHeader?.replace("Bearer ", "");
let ip = "";
@@ -57,7 +60,7 @@ const auth = function (request) {
} catch (err) {
console.log("IP Not Found " + err.message);
}
if (authToken !== API_TOKEN) {
if ((await VerifyAPIKey(authToken)) === false) {
return new Error("invalid token");
}
if (API_IP !== undefined && ip != "") {
@@ -108,13 +111,15 @@ const store = async function (data) {
return { error: err.message, status: 400 };
}
//check if tag is valid
if (!CheckIfValidTag(tag)) {
return { error: "invalid tag", status: 400 };
let monitor = await db.getMonitorByTag(tag);
if (!!!monitor) {
return { error: "no monitor with tag found", status: 400 };
}
if (monitor.status != "ACTIVE") {
return { error: "monitor with the given tag is not active", status: 400 };
}
//get the monitor object matching the tag
let monitors = get(monitorsStore);
const monitor = monitors.find((monitor) => monitor.tag === tag);
await db.insertData({
monitorTag: tag,
@@ -125,7 +130,7 @@ const store = async function (data) {
});
return { status: 200, message: "success at " + data.timestampInSeconds };
};
const GHIssueToKenerIncident = function (issue) {
const GHIssueToKenerIncident = async function (issue) {
if (!!!issue) {
return null;
}
@@ -133,7 +138,7 @@ const GHIssueToKenerIncident = function (issue) {
let issueLabels = issue.labels.map((label) => {
return label.name;
});
let tagsAvailable = GetAllTags();
let tagsAvailable = await GetAllTags();
//get common tags as array
let commonTags = tagsAvailable.filter((tag) => issueLabels.includes(tag));
@@ -174,7 +179,7 @@ const GHIssueToKenerIncident = function (issue) {
}
return resp;
};
const ParseIncidentPayload = function (payload) {
const ParseIncidentPayload = async function (payload) {
let startDatetime = payload.startDatetime; //in utc seconds optional
let endDatetime = payload.endDatetime; //in utc seconds optional
let title = payload.title; //string required
@@ -216,7 +221,7 @@ const ParseIncidentPayload = function (payload) {
return { error: "Invalid impact" };
}
//check if tags are valid
const allTags = GetAllTags();
const allTags = await GetAllTags();
if (tags.some((tag) => allTags.indexOf(tag) === -1)) {
return { error: "Unknown tags" };
}
@@ -247,16 +252,23 @@ const ParseIncidentPayload = function (payload) {
return { title, body, githubLabels };
};
const GetMonitorStatusByTag = function (tag, timestamp) {
if (!CheckIfValidTag(tag)) {
return { error: "invalid tag", status: 400 };
const GetMonitorStatusByTag = async function (tag, timestamp) {
let monitor = await db.getMonitorByTag(tag);
if (!!!monitor) {
return { error: "no monitor with tag found", status: 400 };
}
if (monitor.status != "ACTIVE") {
return { error: "monitor with the given tag is not active", status: 400 };
}
const resp = {
status: null,
uptime: null,
lastUpdatedAt: null
};
let monitors = get(monitorsStore);
let monitors = await GetMonitors({
status: "ACTIVE"
});
const { includeDegradedInDowntime } = monitors.find((monitor) => monitor.tag === tag);
let now = GetMinuteStartNowTimestampUTC();
@@ -265,7 +277,7 @@ const GetMonitorStatusByTag = function (tag, timestamp) {
}
let start = GetDayStartTimestampUTC(now);
let dayDataNew = db.getData(tag, start, now);
let dayDataNew = await db.getData(tag, start, now);
let ups = 0;
let downs = 0;
let degradeds = 0;

View File

@@ -1,3 +1,24 @@
.tabs [data-state="active"] {
border-color: orange;
}
.copybtn .copy-btn {
transform: scale(1);
}
.copybtn .check-btn {
transform: scale(0);
}
.copybtn:focus .copy-btn {
transform: scale(0);
}
.copybtn:focus .check-btn {
transform: scale(1);
}
input,
textarea {
background-color: rgba(0, 0, 0, 0.02) !important;
}
.dark input,
.dark textarea {
background-color: rgba(0, 0, 0, 0.1) !important;
}

View File

@@ -10,6 +10,9 @@ export async function load({ params, route, url, cookies }) {
let tokenData = cookies.get("kener-user");
if (!!tokenData) {
let tokenUser = await VerifyToken(tokenData);
if (!!!tokenUser) {
throw redirect(302, base + "/signin/logout");
}
let userDB = await db.getUserByEmail(tokenUser.email);
if (!!userDB) {
throw redirect(302, base + "/manage");

View File

@@ -6,7 +6,7 @@ import { CreateIssue, SearchIssue } from "$lib/server/github";
export async function POST({ request }) {
const payload = await request.json();
const authError = auth(request);
const authError = await auth(request);
if (authError !== null) {
return json(
{ error: authError.message },
@@ -16,7 +16,7 @@ export async function POST({ request }) {
);
}
let { title, body, githubLabels, error } = ParseIncidentPayload(payload);
let { title, body, githubLabels, error } = await ParseIncidentPayload(payload);
if (error) {
return json(
{ error },
@@ -25,8 +25,6 @@ export async function POST({ request }) {
}
);
}
let site = get(siteStore);
let github = site.github;
githubLabels.push("manual");
let resp = await CreateIssue(title, body, githubLabels);
if (resp === null) {
@@ -38,13 +36,13 @@ export async function POST({ request }) {
);
}
return json(GHIssueToKenerIncident(resp), {
return json(await GHIssueToKenerIncident(resp), {
status: 200
});
}
export async function GET({ request, url }) {
const authError = auth(request);
const authError = await auth(request);
if (authError !== null) {
return json(
{ error: authError.message },
@@ -107,7 +105,9 @@ export async function GET({ request, url }) {
}
const resp = await SearchIssue(filterArray, page, per_page);
const incidents = resp.items.map((issue) => GHIssueToKenerIncident(issue));
const incidents = await Promise.all(
resp.items.map(async (issue) => await GHIssueToKenerIncident(issue))
);
return json(incidents, {
status: 200

View File

@@ -10,7 +10,7 @@ import {
} from "$lib/server/github";
export async function PATCH({ request, params }) {
const authError = auth(request);
const authError = await auth(request);
if (authError !== null) {
return json(
{ error: authError.message },
@@ -29,7 +29,7 @@ export async function PATCH({ request, params }) {
}
);
}
let { title, body, githubLabels, error } = ParseIncidentPayload(payload);
let { title, body, githubLabels, error } = await ParseIncidentPayload(payload);
if (error) {
return json(
{ error },
@@ -48,13 +48,13 @@ export async function PATCH({ request, params }) {
}
);
}
return json(GHIssueToKenerIncident(resp), {
return json(await GHIssueToKenerIncident(resp), {
status: 200
});
}
export async function GET({ request, params }) {
const authError = auth(request);
const authError = await auth(request);
if (authError !== null) {
return json(
{ error: authError.message },
@@ -76,7 +76,7 @@ export async function GET({ request, params }) {
);
}
return json(GHIssueToKenerIncident(issue), {
return json(await GHIssueToKenerIncident(issue), {
status: 200
});
}

View File

@@ -5,7 +5,7 @@ import { auth } from "$lib/server/webhook";
import { AddComment, GetCommentsForIssue } from "$lib/server/github";
export async function GET({ request, params }) {
const authError = auth(request);
const authError = await auth(request);
if (authError !== null) {
return json(
{ error: authError.message },
@@ -41,7 +41,7 @@ export async function GET({ request, params }) {
}
export async function POST({ request, params }) {
// const headers = await request.headers();
const authError = auth(request);
const authError = await auth(request);
if (authError !== null) {
return json(
{ error: authError.message },

View File

@@ -8,7 +8,7 @@ export async function POST({ request, params }) {
const payload = await request.json();
const incidentNumber = params.incidentNumber; //number required
// const headers = await request.headers();
const authError = auth(request);
const authError = await auth(request);
if (authError !== null) {
return json(
{ error: authError.message },
@@ -82,7 +82,7 @@ export async function POST({ request, params }) {
}
);
}
return json(GHIssueToKenerIncident(resp), {
return json(await GHIssueToKenerIncident(resp), {
status: 200
});
}

View File

@@ -4,7 +4,7 @@ import { json } from "@sveltejs/kit";
import { store, auth, GetMonitorStatusByTag } from "$lib/server/webhook";
export async function POST({ request }) {
const payload = await request.json();
const authError = auth(request);
const authError = await auth(request);
if (authError !== null) {
return json(
{ error: authError.message },
@@ -19,7 +19,7 @@ export async function POST({ request }) {
});
}
export async function GET({ request, url }) {
const authError = auth(request);
const authError = await auth(request);
if (authError !== null) {
return json(
{ error: authError.message },
@@ -39,7 +39,7 @@ export async function GET({ request, url }) {
}
);
}
return json(GetMonitorStatusByTag(tag, timestamp), {
return json(await GetMonitorStatusByTag(tag, timestamp), {
status: 200
});
}

View File

@@ -19,6 +19,10 @@ export async function load({ params, route, url, cookies, request }) {
//get user by email
let tokenUser = await VerifyToken(tokenData);
if (!!!tokenUser) {
//redirect to signin page if user is not authenticated
throw redirect(302, base + "/signin/logout");
}
let userDB = await db.getUserByEmail(tokenUser.email);
if (!!!userDB) {
//redirect to signin page if user is not authenticated

View File

@@ -19,10 +19,6 @@
} else {
setMode("light");
}
analyticsEvent("theme_change", {
theme: $mode
});
}
setMode("dark");

View File

@@ -12,6 +12,7 @@
import MonitorsAdd from "$lib/components/manage/monitorsAdd.svelte";
import TriggerInfo from "$lib/components/manage/triggerInfo.svelte";
import AlertsInfo from "$lib/components/manage/alertsInfo.svelte";
import APIKeys from "$lib/components/manage/apiKeys.svelte";
import { Tabs } from "bits-ui";
import { Plane } from "lucide-svelte";
import { onMount } from "svelte";
@@ -84,6 +85,12 @@
>
Alerts
</Tabs.Trigger>
<Tabs.Trigger
value="APIKeys"
class="border-b-2 border-transparent px-2 pb-1 text-sm font-medium"
>
API Keys
</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="SiteInfo" class="min-h-[70vh] pt-3">
<SiteInfo {data} />
@@ -101,7 +108,11 @@
<ThemeInfo {data} />
</Tabs.Content>
<Tabs.Content value="MonitorsAdd" class="min-h-[70vh] pt-3">
<MonitorsAdd categories={data.siteData?.categories} />
<MonitorsAdd
categories={data.siteData?.categories}
colorDown={data.siteData?.colors.DOWN}
colorDegraded={data.siteData?.colors.DEGRADED}
/>
</Tabs.Content>
<Tabs.Content value="TriggerInfo" class="min-h-[70vh] pt-3">
<TriggerInfo />
@@ -109,5 +120,8 @@
<Tabs.Content value="AlertsInfo" class="min-h-[70vh] pt-3">
<AlertsInfo {data} />
</Tabs.Content>
<Tabs.Content value="APIKeys" class="min-h-[70vh] pt-3">
<APIKeys />
</Tabs.Content>
</Tabs.Root>
</div>

View File

@@ -8,12 +8,15 @@ import {
CreateUpdateTrigger,
GetAllTriggers,
UpdateTriggerData,
GetAllAlertsPaginated
GetAllAlertsPaginated,
GetAllAPIKeys,
CreateNewAPIKey,
UpdateApiKeyStatus
} from "$lib/server/controllers/controller.js";
export async function POST({ request }) {
const payload = await request.json();
let action = payload.action;
let data = payload.data;
let data = payload.data || {};
let resp = {};
try {
if (action === "storeSiteData") {
@@ -30,6 +33,12 @@ export async function POST({ request }) {
resp = await UpdateTriggerData(data);
} else if (action == "getAllAlertsPaginated") {
resp = await GetAllAlertsPaginated(data);
} else if (action == "getAPIKeys") {
resp = await GetAllAPIKeys();
} else if (action == "createNewApiKey") {
resp = await CreateNewAPIKey(data);
} else if (action == "updateApiKeyStatus") {
resp = await UpdateApiKeyStatus(data);
}
} catch (error) {
resp = { error: error.message };