mirror of
https://github.com/rajnandan1/kener.git
synced 2026-03-13 14:29:57 -05:00
feat: pre release 3.0.0
This commit is contained in:
5
.gitignore
vendored
5
.gitignore
vendored
@@ -20,4 +20,7 @@ config/static/*
|
||||
db/*
|
||||
!db/.kener
|
||||
database/*
|
||||
!database/.kener
|
||||
!database/.kener
|
||||
|
||||
static/uploads/*
|
||||
!static/uploads/upload.dir
|
||||
15
delay.js
15
delay.js
@@ -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);
|
||||
|
||||
@@ -147,6 +147,7 @@ function IsValidNameServer(nameServer) {
|
||||
const IsValidURL = function (url) {
|
||||
return /^(http|https):\/\/[^ "]+$/.test(url);
|
||||
};
|
||||
|
||||
export {
|
||||
siteDataExtractFromDb,
|
||||
storeSiteData,
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
22
src/lib/server/db/seedMonitorData.js
Normal file
22
src/lib/server/db/seedMonitorData.js
Normal 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
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
46
src/routes/(manage)/manage/upload/+server.js
Normal file
46
src/routes/(manage)/manage/upload/+server.js
Normal 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 });
|
||||
}
|
||||
1
static/uploads/upload.dir
Normal file
1
static/uploads/upload.dir
Normal file
@@ -0,0 +1 @@
|
||||
this is the upload directory
|
||||
BIN
test.db-shm
BIN
test.db-shm
Binary file not shown.
BIN
test.db-wal
BIN
test.db-wal
Binary file not shown.
Reference in New Issue
Block a user