feat: pre release 3.0.0

This commit is contained in:
Raj Nandan Sharma
2024-12-28 19:21:23 +05:30
parent 8b9f576b30
commit f1be4a4db0
30 changed files with 564 additions and 165 deletions

5
.gitignore vendored
View File

@@ -20,4 +20,7 @@ config/static/*
db/*
!db/.kener
database/*
!database/.kener
!database/.kener
static/uploads/*
!static/uploads/upload.dir

View File

@@ -1,17 +1,20 @@
import fs from "fs-extra";
import path from "path";
import db from "./src/lib/server/db/db.js";
let maxWait = 5000;
let interval = 1000;
let waitTime = 0;
let serverDataPath = path.join(process.cwd(), "database", "server.json");
let siteDataPath = path.join(process.cwd(), "database", "site.json");
let monitorsDataPath = path.join(process.cwd(), "database", "monitors.json");
async function allFilesExist() {
let tablesCreated = (await db.checkTables()).map((table) => table.name);
let tablesRequired = ["MonitoringData", "MonitorAlerts", "SiteData", "Monitors", "Alerts"];
let tablesRequired = [
"MonitoringData",
"MonitorAlerts",
"SiteData",
"Monitors",
"Triggers",
"Users",
"ApiKeys"
];
for (let table of tablesRequired) {
let tableExists = tablesCreated.includes(table);

View File

@@ -147,6 +147,7 @@ function IsValidNameServer(nameServer) {
const IsValidURL = function (url) {
return /^(http|https):\/\/[^ "]+$/.test(url);
};
export {
siteDataExtractFromDb,
storeSiteData,

View File

@@ -140,20 +140,38 @@
}
}
function handleFileChangeLogo(event, i) {
async function handleFileChangeLogo(event, i) {
const file = event.target.files[0];
if (file) {
if (file.size > 100000) {
alert("File size should be less than 100KB");
return;
}
if (!file) {
alert("Please select a file to upload.");
return;
}
const reader = new FileReader();
reader.onload = () => {
nav[i].iconURL = reader.result;
};
reader.readAsDataURL(file);
if (file.size > 100000) {
alert("File size should be less than 100KB");
return;
}
nav[i].uploading = true;
const formData = new FormData();
formData.append("image", file);
try {
const response = await fetch("/manage/upload", {
method: "POST",
body: formData
});
if (response.ok) {
const result = await response.json();
nav[i].iconURL = base + "/uploads/" + result.filename;
} else {
alert("Failed to upload file.");
}
} catch (error) {
alert("An error occurred while uploading the file.");
} finally {
nav[i].uploading = false;
}
}
function addNewRow() {
@@ -307,6 +325,7 @@
<Input
class=""
id="logo"
disabled={!!navItem.uploading}
type="file"
accept=".jpg, .jpeg, .png"
on:change={(e) => {

View File

@@ -33,17 +33,42 @@
let formState = "idle";
export let newMonitor;
let loadingLogo = false;
let selectedCategory = categories.find((category) => category.name === newMonitor.categoryName);
function handleFileChangeLogo(event) {
async function handleFileChangeLogo(event) {
const file = event.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = () => {
newMonitor.image = reader.result;
};
reader.readAsDataURL(file);
if (!file) {
event.target.value = "";
alert("Please select a file to upload.");
return;
}
if (file.size > 100000) {
event.target.value = "";
alert("File size should be less than 100KB");
return;
}
loadingLogo = true;
const formData = new FormData();
formData.append("image", file);
try {
const response = await fetch("/manage/upload", {
method: "POST",
body: formData
});
if (response.ok) {
const result = await response.json();
newMonitor.image = base + "/uploads/" + result.filename;
} else {
alert("Failed to upload file.");
}
} catch (error) {
alert("An error occurred while uploading the file.");
} finally {
loadingLogo = false;
}
}
function addHeader() {
@@ -257,6 +282,7 @@
class="w-1/2"
id="logo"
type="file"
disabled={loadingLogo}
accept=".jpg, .jpeg, .png"
on:change={(e) => {
handleFileChangeLogo(e);

View File

@@ -260,25 +260,27 @@
<Card.Content>
<div class="flex justify-between gap-4">
<div class="">
<Label class="text-xs">Tag</Label>
<Label class="text-xs font-semibold text-muted-foreground">Tag</Label>
<p class="text-sm font-semibold">
{monitor.tag}
</p>
</div>
<div class="">
<Label class="text-xs">Monitor Type</Label>
<Label class="text-xs font-semibold text-muted-foreground"
>Monitor Type</Label
>
<p class="text-sm font-semibold">
{monitor.monitorType}
</p>
</div>
<div class="">
<Label class="text-xs">Cron</Label>
<Label class="text-xs font-semibold text-muted-foreground">Cron</Label>
<p class="text-sm font-semibold">
{monitor.cron}
</p>
</div>
<div class="">
<Label class="text-xs">Category</Label>
<Label class="text-xs font-semibold text-muted-foreground">Category</Label>
<p class="text-sm font-semibold">
{!!monitor.categoryName ? monitor.categoryName : "-"}
</p>

View File

@@ -149,7 +149,7 @@
<Card.Content>
<form class="mx-auto mt-4 space-y-4" use:autoAnimate on:submit|preventDefault={formSubmit}>
{#each metaTags as metaTag, i}
<div class="flex w-full flex-row justify-evenly gap-2">
<div class="flex w-full flex-row justify-between gap-2">
<div class="grid grid-cols-12 gap-x-2">
<div class="col-span-4">
<Label for="key">

View File

@@ -6,7 +6,7 @@
import { siteDataExtractFromDb, storeSiteData } from "$lib/clientTools.js";
import { tooltipAction } from "svelte-legos";
import { base } from "$app/paths";
import { Loader, Info } from "lucide-svelte";
import { Loader, Info, X } from "lucide-svelte";
import { Tooltip } from "bits-ui";
export let data;
@@ -20,6 +20,8 @@
favicon: ""
};
let logoFile;
let formErrorMessage = "";
siteInformation = siteDataExtractFromDb(data.siteData, siteInformation);
@@ -38,26 +40,104 @@
}
}
function handleFileChangeLogo(event) {
const file = event.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = () => {
siteInformation.logo = reader.result;
};
reader.readAsDataURL(file);
}
}
// function handleFileChangeLogo(event) {
// const file = event.target.files[0];
// if (file) {
// const reader = new FileReader();
// reader.onload = () => {
// siteInformation.logo = reader.result;
// };
// reader.readAsDataURL(file);
// }
// }
function handleFileChangeFavicon(event) {
const file = event.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = () => {
siteInformation.favicon = reader.result;
uploadFunction(reader.result);
};
reader.readAsDataURL(file);
}
}
let uploadLogoStatus = "";
let uploadingLogo = false;
async function handleFileChangeLogo(event) {
event.preventDefault();
uploadLogoStatus = "";
const file = event.target.files[0];
if (!file) {
uploadLogoStatus = "Please select a file to upload.";
return;
}
if (file.size > 100000) {
uploadLogoStatus = "File size should be less than 100KB.";
return;
}
const formData = new FormData();
formData.append("image", file);
uploadingLogo = true;
try {
const response = await fetch("/manage/upload", {
method: "POST",
body: formData
});
if (response.ok) {
const result = await response.json();
siteInformation.logo = base + "/uploads/" + result.filename;
} else {
uploadLogoStatus = "Failed to upload file.";
}
} catch (error) {
uploadLogoStatus = "An error occurred while uploading the file.";
} finally {
uploadingLogo = false;
}
}
let uploadFaviconStatus = "";
let uploadingFavicon = false;
async function handleFaviconChangeLogo(event) {
event.preventDefault();
uploadFaviconStatus = "";
const file = event.target.files[0];
if (!file) {
uploadFaviconStatus = "Please select a file to upload.";
return;
}
if (file.size > 20000) {
uploadFaviconStatus = "File size should be less than 20KB.";
return;
}
const formData = new FormData();
formData.append("image", file);
uploadingFavicon = true;
try {
const response = await fetch("/manage/upload", {
method: "POST",
body: formData
});
if (response.ok) {
const result = await response.json();
siteInformation.favicon = base + "/uploads/" + result.filename;
} else {
uploadFaviconStatus = "Failed to upload file.";
}
} catch (error) {
uploadFaviconStatus = "An error occurred while uploading the file.";
} finally {
uploadingFavicon = false;
}
}
</script>
<Card.Root class="mt-4">
@@ -182,40 +262,113 @@
/>
</div>
</div>
<div class="flex w-full flex-row justify-evenly gap-2">
<div class="w-full">
<Label for="logo">Site Logo</Label>
{#if !!siteInformation.logo}
<img
src={siteInformation.logo}
class="mt-2 h-[48px] w-[48px] rounded-sm border p-1"
alt=""
/>
{/if}
<Input
class="mt-2"
id="logo"
type="file"
accept=".jpg, .jpeg, .png"
on:change={handleFileChangeLogo}
/>
<div class="flex w-full flex-col justify-evenly gap-2">
<div>
<Label for="logo">Logo</Label>
</div>
<div class="w-full">
<Label for="favicon">Favicon</Label>
{#if !!siteInformation.favicon}
<img
src={siteInformation.favicon}
class="mt-2 h-[48px] w-[48px] rounded-sm border p-1"
alt=""
/>
<div class="flex gap-x-2">
{#if !!siteInformation.logo}
<div class="relative">
<Label
for="logo"
class="inline-block h-[60px] w-[60px] cursor-pointer rounded-sm border p-1"
>
<img
src={siteInformation.logo}
class="w-fit hover:scale-95"
alt=""
/>
</Label>
<div class="absolute -right-2 -top-2">
<Button
variant="secondary"
class="h-5 w-5 rounded-full border p-1"
size="icon"
on:click={() => (siteInformation.logo = "")}
>
<X class="h-4 w-4" />
</Button>
</div>
{#if uploadingLogo}
<div
class="absolute left-1/2 top-1/2 h-5 w-5 -translate-x-1/2 -translate-y-1/2 transform rounded-sm bg-[rgba(0,0,0,0.5)] p-0.5"
>
<Loader class="h-4 w-4 animate-spin" />
</div>
{/if}
</div>
{/if}
<Input
class="mt-2"
id="favicon"
type="file"
accept=".jpg, .jpeg, .png"
on:change={handleFileChangeFavicon}
/>
<div>
<Input
class=""
id="logo"
type="file"
accept=".jpg, .jpeg, .png"
disabled={uploadingLogo}
on:change={handleFileChangeLogo}
/>
<p class="mt-1 text-xs font-medium">
Please upload a square image of max size 100KB
</p>
<p class="mt-1 text-xs font-medium text-destructive">
{uploadLogoStatus}
</p>
</div>
</div>
<div>
<Label for="favicon">Favicon</Label>
</div>
<div class="flex gap-x-2">
{#if !!siteInformation.favicon}
<div class="relative">
<Label
for="favicon"
class="inline-block h-[40px] w-[40px] cursor-pointer rounded-sm border p-1"
>
<img
src={siteInformation.favicon}
class="w-fit hover:scale-95"
alt=""
/>
</Label>
<div class="absolute -right-2 -top-2">
<Button
variant="secondary"
class="h-5 w-5 rounded-full border p-1"
size="icon"
on:click={() => (siteInformation.favicon = "")}
>
<X class="h-4 w-4" />
</Button>
</div>
{#if uploadingFavicon}
<div
class="absolute left-1/2 top-1/2 h-5 w-5 -translate-x-1/2 -translate-y-1/2 transform rounded-sm bg-[rgba(0,0,0,0.5)] p-0.5"
>
<Loader class="h-4 w-4 animate-spin" />
</div>
{/if}
</div>
{/if}
<div>
<Input
class=""
id="logo"
type="file"
accept=".jpg, .jpeg, .png, .ico"
disabled={uploadingFavicon}
on:change={handleFaviconChangeLogo}
/>
<p class="mt-1 text-xs font-medium">
Please upload a square image of max size 20KB
</p>
<p class="mt-1 text-xs font-medium text-destructive">
{uploadFaviconStatus}
</p>
</div>
</div>
</div>
<div class="flex w-full justify-end">

View File

@@ -247,9 +247,9 @@
</div>
</div>
</div>
<div class="chart-status relative col-span-12 mt-1">
<div class="relative col-span-12 mt-1">
<div
class="daygrid90 flex min-h-[60px] overflow-x-auto overflow-y-hidden py-1"
class="daygrid90 flex min-h-[60px] justify-start overflow-x-auto overflow-y-hidden py-1"
use:clickOutsideAction
on:clickoutside={(e) => {
showDailyDataModal = false;
@@ -269,7 +269,7 @@
href="#"
class="oneline h-[34px] w-[6px] border-b-2 {bar.border
? 'border-indigo-400'
: 'border-transparent'} pb-1"
: 'border-transparent'} pb-1"
>
<div
class="oneline-in h-[30px] bg-{bar.cssClass} mx-auto w-[4px] rounded-{monitor.pageData.barRoundness.toUpperCase() ==
@@ -293,7 +293,7 @@
{#if showDailyDataModal}
<div
transition:slide={{ direction: "bottom" }}
class="okclass absolute -left-2 top-10 z-10 mx-auto rounded-sm border bg-card px-[7px] py-[7px] shadow-lg md:w-[560px]"
class="absolute -left-2 top-10 z-10 mx-auto rounded-sm border bg-card px-[7px] py-[7px] shadow-lg md:w-[560px]"
>
<div class="mb-2 flex justify-between text-xs font-semibold">
<span>{dateFetchedFor}</span>

View File

@@ -10,8 +10,8 @@
<div class="{defaultPattern}-pattern"></div>
<header class="blurry-bg relative z-50 mx-auto mt-2">
<div class="container flex h-14 max-w-[840px] items-center">
<header class="blurry-bg sticky top-0 z-50 mx-auto mt-2">
<div class="container flex h-14 max-w-[820px] items-center rounded-md border bg-card px-3">
<a href={data.site.home ? data.site.home : base} class="mr-6 flex items-center space-x-2">
{#if data.site.logo}
<img
@@ -34,15 +34,13 @@
>
{#each data.site.nav as navItem}
<a
href={navItem.url.startsWith("/") ? base + navItem.url : navItem.url}
class="flex"
href={navItem.url}
class="flex decoration-1 hover:underline"
on:click={() => analyticsEvent("nav", navItem.name)}
>
{#if navItem.iconURL}
<img
src={navItem.iconURL.startsWith("/")
? base + navItem.iconURL
: navItem.iconURL}
src={navItem.iconURL}
class="mr-1.5 inline h-4"
alt={navItem.name}
/>

View File

@@ -336,3 +336,12 @@ export const VerifyAPIKey = async (apiKey) => {
} // Adjust this for your DB query
return false;
};
export const IsSetupComplete = async () => {
let data = await db.getAllSiteData();
if (!data) {
return false;
}
return data.length > 0;
};

View File

@@ -1,22 +1,15 @@
// @ts-nocheck
import Sqlite from "./sqlite.js";
import Postgres from "./postgres.js";
import migration2 from "./migration2.js";
import { serverStore } from "../stores/server.js";
import { get } from "svelte/store";
let instance = null;
const server = get(serverStore);
let database = server.database;
if (database === undefined) {
database = {
sqlite: {
dbName: "kener.db"
}
};
}
const supportedDatabases = ["sqlite", "postgres"];
let database = {
sqlite: {
dbName: "kener.local3.db"
}
};
const supportedDatabases = ["sqlite"];
const dbType = Object.keys(database)[0] || "sqlite";
const dbConfig = database[dbType];
@@ -57,7 +50,7 @@ if (dbType === "sqlite") {
instance = new Postgres(dbConfig);
}
migration2(instance, "./database");
//migration2(instance, "./database");
//create anonymous function to call the init function

View File

@@ -0,0 +1,22 @@
let seedMonitorData = [
{
tag: "earth",
name: "Earth - Planet 3",
description:
"Earth is the 3rd planet in our solar system and it is the most majestic one. ",
image: "https://kener.ing/earth.png",
cron: "* * * * *",
defaultStatus: "UP",
status: "ACTIVE",
categoryName: "Home",
monitorType: "NONE",
downTrigger: null,
degradedTrigger: null,
typeData: "",
dayDegradedMinimumCount: 0,
dayDownMinimumCount: 0,
includeDegradedInDowntime: "NO"
}
];
export default seedMonitorData;

File diff suppressed because one or more lines are too long

View File

@@ -22,3 +22,15 @@ textarea {
.dark textarea {
background-color: rgba(0, 0, 0, 0.1) !important;
}
input[type="file"]::file-selector-button {
display: none;
}
input[type="file"] {
font-weight: 500;
padding-top: 10px;
}
.image-upload .has-bg:hover .invisible {
visibility: visible !important;
}

View File

@@ -2,6 +2,7 @@
import "../../app.postcss";
import "../../kener.css";
import "../../manage.css";
import { base } from "$app/paths";
import { setMode, mode, ModeWatcher } from "mode-watcher";
setMode("dark");
</script>
@@ -10,6 +11,8 @@
<svelte:head>
<title>Setup Kener</title>
<meta name="description" content="Set up Kener" />
<meta name="robots" content="noindex" />
<link rel="icon" href="{base}/logo96.png" />
</svelte:head>
<main class="setup">

View File

@@ -2,6 +2,7 @@ export async function load({ params, route, url, parent }) {
//read query parameters
const query = url.searchParams;
return {
error: query.get("error")
error: query.get("error"),
isSecretSet: process.env.KENER_SECRET_KEY !== undefined
};
}

View File

@@ -11,76 +11,110 @@
name: ""
};
export let data = {
error: null
error: null,
isSecretSet: false
};
console.log(">>>>>>---- +page:17 ", data);
</script>
<div class="flex min-h-full flex-col justify-center px-6 py-12 lg:px-8">
<div class="sm:mx-auto sm:w-full sm:max-w-sm">
<img class="mx-auto h-10 w-auto" src="{base}/logo.png" alt="Your Company" />
<h2 class="mt-10 text-center text-2xl/9 font-bold tracking-tight">Set up Kener.ing</h2>
<p class="mt-4 text-center">Welcome to Kener.ing! Let's get you set up.</p>
</div>
{#if data.isSecretSet}
<div class="mt-10 sm:mx-auto sm:w-full sm:max-w-sm">
{#if data.error}
<Alert.Root variant="destructive" class="my-4">
<Alert.Title>Error</Alert.Title>
<Alert.Description>{data.error}</Alert.Description>
</Alert.Root>
{/if}
<div class="mt-10 sm:mx-auto sm:w-full sm:max-w-sm">
{#if data.error}
<Alert.Root variant="destructive" class="my-4">
<Alert.Title>Error</Alert.Title>
<Alert.Description>{data.error}</Alert.Description>
</Alert.Root>
{/if}
<form class="space-y-6" action="{base}/setup/submit" method="POST">
<div>
<label for="email" class="block text-sm/6 font-medium">Name</label>
<div class="mt-2">
<Input
bind:value={form.name}
type="text"
name="name"
id="form_name"
autocomplete="first-name"
placeholder="Jane Doe"
required
/>
</div>
</div>
<div>
<label for="email" class="block text-sm/6 font-medium">Email address</label>
<div class="mt-2">
<Input
bind:value={form.email}
type="email"
name="email"
id="form_email"
autocomplete="email"
placeholder="user@example.com"
required
/>
</div>
</div>
<div>
<div class="flex items-center justify-between">
<label for="password" class="block text-sm/6 font-medium">Password</label>
</div>
<div class="mt-2">
<form class="space-y-6" action="{base}/setup/submit" method="POST">
<div>
<label for="email" class="block text-sm/6 font-medium">Your Name</label>
<div class="mt-2">
<Input
bind:value={form.password}
type="password"
name="password"
id="form_password"
autocomplete="password"
placeholder="***********"
bind:value={form.name}
type="text"
name="name"
id="form_name"
autocomplete="first-name"
placeholder="Jane Doe"
required
/>
</div>
</div>
</div>
<div>
<label for="email" class="block text-sm/6 font-medium">Email address</label>
<div class="mt-2">
<Input
bind:value={form.email}
type="email"
name="email"
id="form_email"
autocomplete="email"
placeholder="user@example.com"
required
/>
<p class="mt-0.5 text-xs text-muted-foreground">
This will email address will use be used to login so that you can manage
this instance of kener.
</p>
</div>
</div>
<div>
<Button type="submit" class="w-full">Let's Go</Button>
<div>
<div class="flex items-center justify-between">
<label for="password" class="block text-sm/6 font-medium">Password</label>
</div>
<div class="mt-2">
<div class="mt-2">
<Input
bind:value={form.password}
type="password"
name="password"
id="form_password"
autocomplete="password"
placeholder="***********"
required
/>
<p class="mt-0.5 text-xs text-muted-foreground">
Please make sure you remember this password. Other wise you will
have to reset it manually from db.
</p>
</div>
</div>
</div>
<div>
<Button type="submit" class="w-full">Let's Go</Button>
</div>
</form>
</div>
{:else}
<div class="mt-10 sm:mx-auto sm:w-full sm:max-w-md">
<div class="rounded-md bg-card p-4">
<p class="text-lg/6">
Environment variable <span class="font-mono text-yellow-500"
>KENER_SECRET_KEY</span
>
not found.
</p>
<hr class="my-2" />
<p class="text-muted-foreground">
Please set the environment variable <span class="font-mono"
>KENER_SECRET_KEY</span
>. Then restart the server / refresh this page. Check the
<a
href="https://kener.ing/docs/environment-vars"
target="_blank"
class="text-primary">documentation</a
> for more Information.
</p>
</div>
</form>
</div>
</div>
{/if}
</div>

View File

@@ -3,8 +3,25 @@ import { json, redirect } from "@sveltejs/kit";
import { base } from "$app/paths";
import db from "$lib/server/db/db.js";
import seedSiteData from "$lib/server/db/seedSiteData.js";
import seedMonitorData from "$lib/server/db/seedMonitorData.js";
import { HashPassword, GenerateSalt } from "$lib/server/controllers/controller.js";
//function to validate a strong password
/**
* Validates a password to ensure it meets the following criteria:
* - Contains at least one digit.
* - Contains at least one lowercase letter.
* - Contains at least one uppercase letter.
* - Contains at least one letter (either lowercase or uppercase).
* - Has a minimum length of 8 characters.
*
* @param {string} password - The password to validate.
* @returns {boolean} - Returns true if the password meets the criteria, otherwise false.
*/
function validatePassword(password) {
return /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[a-zA-Z]).{8,}$/.test(password);
}
export async function POST({ request }) {
//read form post data email and passowrd
const formdata = await request.formData();
@@ -26,6 +43,14 @@ export async function POST({ request }) {
name: name,
role: "admin"
};
//validate password
if (!validatePassword(password)) {
let errorMessage =
"Password must contain at least one digit, one lowercase letter, one uppercase letter, and have a minimum length of 8 characters.";
throw redirect(302, base + "/setup?error=" + errorMessage);
}
await db.insertUser(user);
for (const key in seedSiteData) {
@@ -35,9 +60,13 @@ export async function POST({ request }) {
if (dataType === "object") {
value = JSON.stringify(value);
}
await this.insertOrUpdateSiteData(key, value, dataType);
await db.insertOrUpdateSiteData(key, value, dataType);
}
}
//loop through array seedMonitorData
for (const monitor of seedMonitorData) {
await db.insertMonitor(monitor);
}
throw redirect(302, base + "/signin");
}

View File

@@ -14,10 +14,14 @@
};
</script>
<svelte:head>
<title>Login Kener</title>
</svelte:head>
<div class="flex min-h-full flex-col justify-center px-6 py-12 lg:px-8">
<div class="sm:mx-auto sm:w-full sm:max-w-sm">
<img class="mx-auto h-10 w-auto" src="{base}/logo.png" alt="Your Company" />
<h2 class="mt-10 text-center text-2xl/9 font-bold tracking-tight">Signin</h2>
<h2 class="mt-10 text-center text-2xl/9 font-bold tracking-tight">Sign in</h2>
<p class="mt-4 text-center">Sign in to manage your kener instance.</p>
</div>
<div class="mt-10 sm:mx-auto sm:w-full sm:max-w-sm">

View File

@@ -1,8 +1,19 @@
// @ts-nocheck
import i18n from "$lib/i18n/server";
import { GetAllSiteData } from "$lib/server/controllers/controller.js";
import { redirect } from "@sveltejs/kit";
import { base } from "$app/paths";
import { GetAllSiteData, IsSetupComplete } from "$lib/server/controllers/controller.js";
export async function load({ params, route, url, cookies, request }) {
let isSetupComplete = await IsSetupComplete();
if (!isSetupComplete) {
throw redirect(302, base + "/setup");
}
if (process.env.KENER_SECRET_KEY === undefined) {
throw redirect(302, base + "/setup");
}
let site = await GetAllSiteData();
const headers = request.headers;
const userAgent = headers.get("user-agent");

View File

@@ -11,7 +11,6 @@
import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
import { analyticsEvent } from "$lib/analytics";
import { setMode, mode, ModeWatcher } from "mode-watcher";
// import { Termo } from "termo";
export let data;
let defaultLocaleKey = data.selectedLang;

View File

@@ -11,7 +11,11 @@ function maskString(str) {
export async function load({ params, route, url, cookies, request }) {
let siteData = await GetAllSiteData();
//check if user is authenticated using cookies
if (process.env.KENER_SECRET_KEY === undefined) {
throw redirect(302, base + "/setup");
}
let tokenData = cookies.get("kener-user");
if (!!!tokenData) {
//redirect to signin page if user is not authenticated
throw redirect(302, base + "/signin");

View File

@@ -28,9 +28,11 @@
<svelte:head>
<title>Manage Kener</title>
<meta name="description" content="Manage your Kener project" />
<meta name="robots" content="noindex" />
<link rel="icon" href="{base}/logo96.png" />
</svelte:head>
<header class="sticky inset-x-0 top-0 z-50 mx-auto flex max-w-4xl px-8">
<div class="mt-4 flex w-full justify-between rounded-lg border bg-card px-5 py-4">
<header class="sticky inset-x-0 top-0 z-50 mx-auto mt-4 flex max-w-4xl px-8">
<div class=" flex w-full justify-between rounded-lg border bg-card px-5 py-4">
<div class="mt-2 flex gap-x-1.5">
<img src="{base}/logo.png" alt="Kener" class="inline h-6 w-6" />
<h1 class="font-semibold">Manage Kener</h1>

View File

@@ -11,13 +11,37 @@ import {
GetAllAlertsPaginated,
GetAllAPIKeys,
CreateNewAPIKey,
UpdateApiKeyStatus
UpdateApiKeyStatus,
VerifyToken
} from "$lib/server/controllers/controller.js";
export async function POST({ request }) {
export async function POST({ request, cookies }) {
const payload = await request.json();
let action = payload.action;
let data = payload.data || {};
let resp = {};
let tokenData = cookies.get("kener-user");
if (!!!tokenData) {
return json(
{
error: "Unauthorized"
},
{ status: 401 }
);
}
let tokenUser = await VerifyToken(tokenData);
if (!!!tokenUser) {
//redirect to signin page if user is not authenticated
return json(
{
error: "Unauthorized"
},
{ status: 401 }
);
}
try {
if (action === "storeSiteData") {
resp = await storeSiteData(data);

View File

@@ -0,0 +1,46 @@
// @ts-nocheck
import { writeFileSync } from "fs-extra";
import { randomUUID } from "crypto";
import { json } from "@sveltejs/kit";
import { VerifyToken } from "$lib/server/controllers/controller.js";
export async function POST({ request, cookies }) {
let tokenData = cookies.get("kener-user");
if (!!!tokenData) {
return json(
{
error: "Unauthorized"
},
{ status: 401 }
);
}
let tokenUser = await VerifyToken(tokenData);
if (!!!tokenUser) {
//redirect to signin page if user is not authenticated
return json(
{
error: "Unauthorized"
},
{ status: 401 }
);
}
// Parse the form data from the request
const formData = await request.formData();
// Get the image file from the form data
const imageFile = formData.get("image");
// Generate a unique filename
const filename = `${randomUUID()}-${imageFile.name}`;
// Read the file as a buffer
const fileBuffer = await imageFile.arrayBuffer();
// Save the file to the static directory
writeFileSync(`./static/uploads/${filename}`, Buffer.from(fileBuffer));
// Return a response
return json({ filename });
}

View File

@@ -0,0 +1 @@
this is the upload directory

BIN
test.db

Binary file not shown.

Binary file not shown.

Binary file not shown.