This commit is contained in:
Raj Nandan Sharma
2026-01-22 18:29:42 +05:30
parent 0149b3e61a
commit 6b88cf11a1
225 changed files with 4158 additions and 4484 deletions
Vendored
BIN
View File
Binary file not shown.
+1
View File
@@ -29,3 +29,4 @@ static/uploads/*
!static/uploads/upload.dir
temp.txt
temp.js
.DS_Store
+1 -3
View File
@@ -24,9 +24,7 @@ export function up(knex) {
table.timestamp("updated_at").defaultTo(knex.fn.now());
})
// Add index to monitor_alerts table
.raw(
"CREATE INDEX idx_monitor_tag_created_at ON monitor_alerts (monitor_tag, created_at)"
)
.raw("CREATE INDEX idx_monitor_tag_created_at ON monitor_alerts (monitor_tag, created_at)")
.createTable("site_data", (table) => {
table.increments("id").primary();
table.string("key", 255).notNullable().unique();
+2 -359
View File
@@ -128,8 +128,8 @@
}
},
"../aven": {
"name": "@rajnandan1/aven",
"version": "0.1.1",
"name": "@rajnandan1/atticus",
"version": "1.0.0",
"license": "MIT",
"dependencies": {
"@openai/agents": "^0.3.7",
@@ -3008,17 +3008,6 @@
"hono": "^4"
}
},
"node_modules/@modelcontextprotocol/sdk/node_modules/hono": {
"version": "4.11.5",
"resolved": "https://registry.npmjs.org/hono/-/hono-4.11.5.tgz",
"integrity": "sha512-WemPi9/WfyMwZs+ZUXdiwcCh9Y+m7L+8vki9MzDw3jJ+W9Lc+12HGsd368Qc1vZi1xwW8BWMMsnK5efYKPdt4g==",
"license": "MIT",
"optional": true,
"peer": true,
"engines": {
"node": ">=16.9.0"
}
},
"node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.3.tgz",
@@ -3168,29 +3157,6 @@
}
}
},
"node_modules/@openai/agents-core/node_modules/ws": {
"version": "8.19.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
"integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
"license": "MIT",
"optional": true,
"peer": true,
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/@openai/agents-openai": {
"version": "0.3.9",
"resolved": "https://registry.npmjs.org/@openai/agents-openai/-/agents-openai-0.3.9.tgz",
@@ -3226,29 +3192,6 @@
}
}
},
"node_modules/@openai/agents-openai/node_modules/ws": {
"version": "8.19.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
"integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
"license": "MIT",
"optional": true,
"peer": true,
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/@openai/agents-realtime": {
"version": "0.3.9",
"resolved": "https://registry.npmjs.org/@openai/agents-realtime/-/agents-realtime-0.3.9.tgz",
@@ -3306,29 +3249,6 @@
}
}
},
"node_modules/@openai/agents/node_modules/ws": {
"version": "8.19.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
"integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
"license": "MIT",
"optional": true,
"peer": true,
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/@polka/url": {
"version": "1.0.0-next.29",
"resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz",
@@ -8291,268 +8211,6 @@
"svelte": "^5.7.0"
}
},
"node_modules/lightningcss": {
"version": "1.31.1",
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.31.1.tgz",
"integrity": "sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ==",
"license": "MPL-2.0",
"optional": true,
"peer": true,
"dependencies": {
"detect-libc": "^2.0.3"
},
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
},
"optionalDependencies": {
"lightningcss-android-arm64": "1.31.1",
"lightningcss-darwin-arm64": "1.31.1",
"lightningcss-darwin-x64": "1.31.1",
"lightningcss-freebsd-x64": "1.31.1",
"lightningcss-linux-arm-gnueabihf": "1.31.1",
"lightningcss-linux-arm64-gnu": "1.31.1",
"lightningcss-linux-arm64-musl": "1.31.1",
"lightningcss-linux-x64-gnu": "1.31.1",
"lightningcss-linux-x64-musl": "1.31.1",
"lightningcss-win32-arm64-msvc": "1.31.1",
"lightningcss-win32-x64-msvc": "1.31.1"
}
},
"node_modules/lightningcss-android-arm64": {
"version": "1.31.1",
"resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.31.1.tgz",
"integrity": "sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg==",
"cpu": [
"arm64"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"android"
],
"peer": true,
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-darwin-arm64": {
"version": "1.31.1",
"resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.31.1.tgz",
"integrity": "sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg==",
"cpu": [
"arm64"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"darwin"
],
"peer": true,
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-darwin-x64": {
"version": "1.31.1",
"resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.31.1.tgz",
"integrity": "sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA==",
"cpu": [
"x64"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"darwin"
],
"peer": true,
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-freebsd-x64": {
"version": "1.31.1",
"resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.31.1.tgz",
"integrity": "sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A==",
"cpu": [
"x64"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"freebsd"
],
"peer": true,
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-arm-gnueabihf": {
"version": "1.31.1",
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.31.1.tgz",
"integrity": "sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g==",
"cpu": [
"arm"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-arm64-gnu": {
"version": "1.31.1",
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.31.1.tgz",
"integrity": "sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg==",
"cpu": [
"arm64"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-arm64-musl": {
"version": "1.31.1",
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.31.1.tgz",
"integrity": "sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==",
"cpu": [
"arm64"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-x64-gnu": {
"version": "1.31.1",
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.31.1.tgz",
"integrity": "sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==",
"cpu": [
"x64"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-x64-musl": {
"version": "1.31.1",
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.31.1.tgz",
"integrity": "sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==",
"cpu": [
"x64"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-win32-arm64-msvc": {
"version": "1.31.1",
"resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.31.1.tgz",
"integrity": "sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w==",
"cpu": [
"arm64"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"win32"
],
"peer": true,
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-win32-x64-msvc": {
"version": "1.31.1",
"resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.31.1.tgz",
"integrity": "sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw==",
"cpu": [
"x64"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"win32"
],
"peer": true,
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lilconfig": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
@@ -12839,21 +12497,6 @@
}
}
},
"node_modules/svelte-check/node_modules/picomatch": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/svelte-codemirror-editor": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/svelte-codemirror-editor/-/svelte-codemirror-editor-2.1.0.tgz",
+2
View File
@@ -21,6 +21,8 @@ export async function seed(knex: Knex): Promise<void> {
day_degraded_minimum_count: monitor.day_degraded_minimum_count,
day_down_minimum_count: monitor.day_down_minimum_count,
include_degraded_in_downtime: monitor.include_degraded_in_downtime,
is_hidden: monitor.is_hidden || "NO",
monitor_settings_json: monitor.monitor_settings_json || null,
created_at: knex.fn.now(),
updated_at: knex.fn.now(),
});
+2 -2
View File
@@ -64,7 +64,7 @@
{/if}
<header class="sticky top-0 z-50 mx-auto md:mt-2">
<div class="container flex h-14 max-w-[820px] items-center border bg-card px-3 md:rounded-md">
<div class="bg-card container flex h-14 max-w-[820px] items-center border px-3 md:rounded-md">
<a rel="external" href={data.site.home ? data.site.home : base} class="mr-6 flex items-center space-x-2">
{#if data.site.logo}
<GMI src={data.site.logo} classList="w-8" alt={data.site.title} srcset="" />
@@ -82,7 +82,7 @@
<a
rel="external"
href={navItem.url}
class="flex rounded-md px-3 py-2 text-card-foreground transition-all ease-linear hover:bg-background"
class="text-card-foreground hover:bg-background flex rounded-md px-3 py-2 transition-all ease-linear"
on:click={() =>
analyticsEvent("navigation", {
name: navItem.name
+5 -5
View File
@@ -140,7 +140,7 @@
<h2 class="mb-1 text-sm font-semibold">
{l(lang, "Share")}
</h2>
<p class="mb-2 text-xs text-muted-foreground">
<p class="text-muted-foreground mb-2 text-xs">
{l(lang, "Share this monitor using a link with others")}
</p>
<Button class="h-8 px-2 pr-4 text-xs font-semibold" variant="secondary" on:click={copyLinkToClipboard}>
@@ -160,7 +160,7 @@
href={pathMonitorLink}
target="_blank"
variant="link"
class="h-8 px-3 text-xs font-semibold text-muted-foreground"
class="text-muted-foreground h-8 px-3 text-xs font-semibold"
>
<ExternalLink class="inline" size={12} />
</Button>
@@ -170,7 +170,7 @@
<h2 class="mb-1 text-sm font-semibold">
{l(lang, "Embed")}
</h2>
<p class="mb-1 text-xs text-muted-foreground">
<p class="text-muted-foreground mb-1 text-xs">
{l(lang, "Embed this monitor using &#x3C;script&#x3E; or &#x3C;iframe&#x3E; in your app.")}
</p>
<div class="mb-4 grid grid-cols-2 gap-2">
@@ -226,7 +226,7 @@
<h2 class="mb-1 text-sm font-semibold">
{l(lang, "Badge")}
</h2>
<p class="mb-2 text-xs text-muted-foreground">
<p class="text-muted-foreground mb-2 text-xs">
{l(lang, "Get SVG badge for this monitor")}
</p>
<Button class="h-8 px-2 pr-4 text-xs" variant="secondary" on:click={copyStatusBadge}>
@@ -263,7 +263,7 @@
<h2 class="mb-1 text-sm font-semibold">
{l(lang, "LIVE Status")}
</h2>
<p class="mb-2 text-xs text-muted-foreground">
<p class="text-muted-foreground mb-2 text-xs">
{l(lang, "Get a LIVE Status for this monitor")}
</p>
<Button class="h-8 px-2 pr-4 text-xs" variant="secondary" on:click={copyDotStandard}>
@@ -2,11 +2,7 @@
import { Accordion as AccordionPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
let {
ref = $bindable(null),
class: className,
...restProps
}: AccordionPrimitive.ItemProps = $props();
let { ref = $bindable(null), class: className, ...restProps }: AccordionPrimitive.ItemProps = $props();
</script>
<AccordionPrimitive.Item
@@ -1,16 +1,7 @@
<script lang="ts">
import { Accordion as AccordionPrimitive } from "bits-ui";
let {
ref = $bindable(null),
value = $bindable(),
...restProps
}: AccordionPrimitive.RootProps = $props();
let { ref = $bindable(null), value = $bindable(), ...restProps }: AccordionPrimitive.RootProps = $props();
</script>
<AccordionPrimitive.Root
bind:ref
bind:value={value as never}
data-slot="accordion"
{...restProps}
/>
<AccordionPrimitive.Root bind:ref bind:value={value as never} data-slot="accordion" {...restProps} />
+5 -12
View File
@@ -6,13 +6,12 @@
variants: {
variant: {
default: "bg-card text-card-foreground",
destructive:
"text-destructive bg-card *:data-[slot=alert-description]:text-destructive/90 [&>svg]:text-current",
},
destructive: "text-destructive bg-card *:data-[slot=alert-description]:text-destructive/90 [&>svg]:text-current"
}
},
defaultVariants: {
variant: "default",
},
variant: "default"
}
});
export type AlertVariant = VariantProps<typeof alertVariants>["variant"];
@@ -33,12 +32,6 @@
} = $props();
</script>
<div
bind:this={ref}
data-slot="alert"
class={cn(alertVariants({ variant }), className)}
{...restProps}
role="alert"
>
<div bind:this={ref} data-slot="alert" class={cn(alertVariants({ variant }), className)} {...restProps} role="alert">
{@render children?.()}
</div>
@@ -2,11 +2,7 @@
import { Avatar as AvatarPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
let {
ref = $bindable(null),
class: className,
...restProps
}: AvatarPrimitive.FallbackProps = $props();
let { ref = $bindable(null), class: className, ...restProps }: AvatarPrimitive.FallbackProps = $props();
</script>
<AvatarPrimitive.Fallback
@@ -2,11 +2,7 @@
import { Avatar as AvatarPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
let {
ref = $bindable(null),
class: className,
...restProps
}: AvatarPrimitive.ImageProps = $props();
let { ref = $bindable(null), class: className, ...restProps }: AvatarPrimitive.ImageProps = $props();
</script>
<AvatarPrimitive.Image
+6 -8
View File
@@ -5,18 +5,16 @@
base: "focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-full border px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] [&>svg]:pointer-events-none [&>svg]:size-3",
variants: {
variant: {
default:
"bg-primary text-primary-foreground [a&]:hover:bg-primary/90 border-transparent",
secondary:
"bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90 border-transparent",
default: "bg-primary text-primary-foreground [a&]:hover:bg-primary/90 border-transparent",
secondary: "bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90 border-transparent",
destructive:
"bg-destructive [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/70 border-transparent text-white",
outline: "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
},
outline: "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground"
}
},
defaultVariants: {
variant: "default",
},
variant: "default"
}
});
export type BadgeVariant = VariantProps<typeof badgeVariants>["variant"];
@@ -2,12 +2,7 @@
import type { HTMLLiAttributes } from "svelte/elements";
import { cn, type WithElementRef } from "$lib/utils.js";
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLLiAttributes> = $props();
let { ref = $bindable(null), class: className, children, ...restProps }: WithElementRef<HTMLLiAttributes> = $props();
</script>
<li
@@ -18,7 +18,7 @@
"data-slot": "breadcrumb-link",
class: cn("hover:text-foreground transition-colors", className),
href,
...restProps,
...restProps
});
</script>
@@ -2,21 +2,13 @@
import type { HTMLOlAttributes } from "svelte/elements";
import { cn, type WithElementRef } from "$lib/utils.js";
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLOlAttributes> = $props();
let { ref = $bindable(null), class: className, children, ...restProps }: WithElementRef<HTMLOlAttributes> = $props();
</script>
<ol
bind:this={ref}
data-slot="breadcrumb-list"
class={cn(
"text-muted-foreground flex flex-wrap items-center gap-1.5 text-sm break-words sm:gap-2.5",
className
)}
class={cn("text-muted-foreground flex flex-wrap items-center gap-1.5 text-sm break-words sm:gap-2.5", className)}
{...restProps}
>
{@render children?.()}
@@ -3,12 +3,7 @@
import { cn, type WithElementRef } from "$lib/utils.js";
import type { HTMLLiAttributes } from "svelte/elements";
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLLiAttributes> = $props();
let { ref = $bindable(null), class: className, children, ...restProps }: WithElementRef<HTMLLiAttributes> = $props();
</script>
<li
@@ -10,12 +10,6 @@
}: WithElementRef<HTMLAttributes<HTMLElement>> = $props();
</script>
<nav
bind:this={ref}
data-slot="breadcrumb"
class={className}
aria-label="breadcrumb"
{...restProps}
>
<nav bind:this={ref} data-slot="breadcrumb" class={className} aria-label="breadcrumb" {...restProps}>
{@render children?.()}
</nav>
@@ -17,7 +17,7 @@
class: cn(
"bg-muted flex items-center gap-2 rounded-md border px-4 text-sm font-medium shadow-xs [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
className
),
)
});
</script>
@@ -8,12 +8,12 @@
horizontal:
"[&>*:not(:first-child)]:rounded-s-none [&>*:not(:first-child)]:border-s-0 [&>*:not(:last-child)]:rounded-e-none",
vertical:
"flex-col [&>*:not(:first-child)]:rounded-t-none [&>*:not(:first-child)]:border-t-0 [&>*:not(:last-child)]:rounded-b-none",
},
"flex-col [&>*:not(:first-child)]:rounded-t-none [&>*:not(:first-child)]:border-t-0 [&>*:not(:last-child)]:rounded-b-none"
}
},
defaultVariants: {
orientation: "horizontal",
},
orientation: "horizontal"
}
});
export type ButtonGroupOrientation = VariantProps<typeof buttonGroupVariants>["orientation"];
+5 -5
View File
@@ -14,7 +14,7 @@
"bg-background hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 border shadow-xs",
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80 shadow-xs",
ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
link: "text-primary underline-offset-4 hover:underline",
link: "text-primary underline-offset-4 hover:underline"
},
size: {
default: "h-9 px-4 py-2 has-[>svg]:px-3",
@@ -22,13 +22,13 @@
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
icon: "size-9",
"icon-sm": "size-8",
"icon-lg": "size-10",
},
"icon-lg": "size-10"
}
},
defaultVariants: {
variant: "default",
size: "default",
},
size: "default"
}
});
export type ButtonVariant = VariantProps<typeof buttonVariants>["variant"];
+1 -6
View File
@@ -1,9 +1,4 @@
import Root, {
type ButtonProps,
type ButtonSize,
type ButtonVariant,
buttonVariants,
} from "./button.svelte";
import Root, { type ButtonProps, type ButtonSize, type ButtonVariant, buttonVariants } from "./button.svelte";
export {
Root,
@@ -10,11 +10,6 @@
}: WithElementRef<HTMLAttributes<HTMLParagraphElement>> = $props();
</script>
<p
bind:this={ref}
data-slot="card-description"
class={cn("text-muted-foreground text-sm", className)}
{...restProps}
>
<p bind:this={ref} data-slot="card-description" class={cn("text-muted-foreground text-sm", className)} {...restProps}>
{@render children?.()}
</p>
+1 -6
View File
@@ -10,11 +10,6 @@
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script>
<div
bind:this={ref}
data-slot="card-title"
class={cn("leading-none font-semibold", className)}
{...restProps}
>
<div bind:this={ref} data-slot="card-title" class={cn("leading-none font-semibold", className)} {...restProps}>
{@render children?.()}
</div>
+1 -4
View File
@@ -13,10 +13,7 @@
<div
bind:this={ref}
data-slot="card"
class={cn(
"bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
className
)}
class={cn("bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm", className)}
{...restProps}
>
{@render children?.()}
@@ -22,7 +22,7 @@
setChartContext({
get config() {
return config;
},
}
});
</script>
@@ -42,7 +42,7 @@
item: TooltipPayload;
index: number;
payload: TooltipPayload[];
},
}
]
>;
} = $props();
@@ -111,7 +111,7 @@
name: item.name,
item,
index: i,
payload: tooltipCtx.payload,
payload: tooltipCtx.payload
})}
{:else}
{#if itemConfig?.icon}
@@ -119,23 +119,16 @@
{:else if !hideIndicator}
<div
style="--color-bg: {indicatorColor}; --color-border: {indicatorColor};"
class={cn(
"shrink-0 rounded-[2px] border-(--color-border) bg-(--color-bg)",
{
class={cn("shrink-0 rounded-[2px] border-(--color-border) bg-(--color-bg)", {
"size-2.5": indicator === "dot",
"h-full w-1": indicator === "line",
"w-0 border-[1.5px] border-dashed bg-transparent":
indicator === "dashed",
"my-0.5": nestLabel && indicator === "dashed",
}
)}
"w-0 border-[1.5px] border-dashed bg-transparent": indicator === "dashed",
"my-0.5": nestLabel && indicator === "dashed"
})}
></div>
{/if}
<div
class={cn(
"flex flex-1 shrink-0 justify-between leading-none",
nestLabel ? "items-end" : "items-center"
)}
class={cn("flex flex-1 shrink-0 justify-between leading-none", nestLabel ? "items-end" : "items-center")}
>
<div class="grid gap-1.5">
{#if nestLabel}
+3 -12
View File
@@ -7,24 +7,15 @@ export type ChartConfig = {
[k in string]: {
label?: string;
icon?: Component;
} & (
| { color?: string; theme?: never }
| { color?: never; theme: Record<keyof typeof THEMES, string> }
);
} & ({ color?: string; theme?: never } | { color?: never; theme: Record<keyof typeof THEMES, string> });
};
export type ExtractSnippetParams<T> = T extends Snippet<[infer P]> ? P : never;
export type TooltipPayload = ExtractSnippetParams<
ComponentProps<typeof Tooltip.Root>["children"]
>["payload"][number];
export type TooltipPayload = ExtractSnippetParams<ComponentProps<typeof Tooltip.Root>["children"]>["payload"][number];
// Helper to extract item config from a payload.
export function getPayloadConfigFromPayload(
config: ChartConfig,
payload: TooltipPayload,
key: string
) {
export function getPayloadConfigFromPayload(config: ChartConfig, payload: TooltipPayload, key: string) {
if (typeof payload !== "object" || payload === null) return undefined;
const payloadPayload =
@@ -2,11 +2,7 @@
import { Dialog as DialogPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
let {
ref = $bindable(null),
class: className,
...restProps
}: DialogPrimitive.DescriptionProps = $props();
let { ref = $bindable(null), class: className, ...restProps }: DialogPrimitive.DescriptionProps = $props();
</script>
<DialogPrimitive.Description
@@ -2,11 +2,7 @@
import { Dialog as DialogPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
let {
ref = $bindable(null),
class: className,
...restProps
}: DialogPrimitive.OverlayProps = $props();
let { ref = $bindable(null), class: className, ...restProps }: DialogPrimitive.OverlayProps = $props();
</script>
<DialogPrimitive.Overlay
@@ -2,11 +2,7 @@
import { Dialog as DialogPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
let {
ref = $bindable(null),
class: className,
...restProps
}: DialogPrimitive.TitleProps = $props();
let { ref = $bindable(null), class: className, ...restProps }: DialogPrimitive.TitleProps = $props();
</script>
<DialogPrimitive.Title
@@ -8,9 +8,4 @@
}: DropdownMenuPrimitive.CheckboxGroupProps = $props();
</script>
<DropdownMenuPrimitive.CheckboxGroup
bind:ref
bind:value
data-slot="dropdown-menu-checkbox-group"
{...restProps}
/>
<DropdownMenuPrimitive.CheckboxGroup bind:ref bind:value data-slot="dropdown-menu-checkbox-group" {...restProps} />
@@ -29,9 +29,7 @@
{...restProps}
>
{#snippet children({ checked, indeterminate })}
<span
class="pointer-events-none absolute start-2 flex size-3.5 items-center justify-center"
>
<span class="pointer-events-none absolute start-2 flex size-3.5 items-center justify-center">
{#if indeterminate}
<MinusIcon class="size-4" />
{:else}
@@ -1,16 +1,7 @@
<script lang="ts">
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
let {
ref = $bindable(null),
value = $bindable(),
...restProps
}: DropdownMenuPrimitive.RadioGroupProps = $props();
let { ref = $bindable(null), value = $bindable(), ...restProps }: DropdownMenuPrimitive.RadioGroupProps = $props();
</script>
<DropdownMenuPrimitive.RadioGroup
bind:ref
bind:value
data-slot="dropdown-menu-radio-group"
{...restProps}
/>
<DropdownMenuPrimitive.RadioGroup bind:ref bind:value data-slot="dropdown-menu-radio-group" {...restProps} />
@@ -21,9 +21,7 @@
{...restProps}
>
{#snippet children({ checked })}
<span
class="pointer-events-none absolute start-2 flex size-3.5 items-center justify-center"
>
<span class="pointer-events-none absolute start-2 flex size-3.5 items-center justify-center">
{#if checked}
<CircleIcon class="size-2 fill-current" />
{/if}
@@ -2,11 +2,7 @@
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
let {
ref = $bindable(null),
class: className,
...restProps
}: DropdownMenuPrimitive.SeparatorProps = $props();
let { ref = $bindable(null), class: className, ...restProps }: DropdownMenuPrimitive.SeparatorProps = $props();
</script>
<DropdownMenuPrimitive.Separator
@@ -2,11 +2,7 @@
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
let {
ref = $bindable(null),
class: className,
...restProps
}: DropdownMenuPrimitive.SubContentProps = $props();
let { ref = $bindable(null), class: className, ...restProps }: DropdownMenuPrimitive.SubContentProps = $props();
</script>
<DropdownMenuPrimitive.SubContent
@@ -3,12 +3,7 @@
import { cn } from "$lib/utils.js";
import type { ComponentProps } from "svelte";
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: ComponentProps<typeof Label> = $props();
let { ref = $bindable(null), class: className, children, ...restProps }: ComponentProps<typeof Label> = $props();
</script>
<Label
@@ -17,12 +17,7 @@
bind:this={ref}
data-slot="field-legend"
data-variant={variant}
class={cn(
"mb-3 font-medium",
"data-[variant=legend]:text-base",
"data-[variant=label]:text-sm",
className
)}
class={cn("mb-3 font-medium", "data-[variant=legend]:text-base", "data-[variant=label]:text-sm", className)}
{...restProps}
>
{@render children?.()}
@@ -20,10 +20,7 @@
bind:this={ref}
data-slot="field-separator"
data-content={hasContent}
class={cn(
"relative -my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2",
className
)}
class={cn("relative -my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2", className)}
{...restProps}
>
<Separator class="absolute inset-0 top-1/2" />
+6 -6
View File
@@ -9,18 +9,18 @@
horizontal: [
"flex-row items-center",
"[&>[data-slot=field-label]]:flex-auto",
"has-[>[data-slot=field-content]]:items-start has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
"has-[>[data-slot=field-content]]:items-start has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px"
],
responsive: [
"flex-col @md/field-group:flex-row @md/field-group:items-center [&>*]:w-full @md/field-group:[&>*]:w-auto [&>.sr-only]:w-auto",
"@md/field-group:[&>[data-slot=field-label]]:flex-auto",
"@md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
],
},
"@md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px"
]
}
},
defaultVariants: {
orientation: "vertical",
},
orientation: "vertical"
}
});
export type FieldOrientation = VariantProps<typeof fieldVariants>["orientation"];
@@ -4,19 +4,16 @@
base: "text-muted-foreground flex h-auto cursor-text items-center justify-center gap-2 py-1.5 text-sm font-medium select-none group-data-[disabled=true]/input-group:opacity-50 [&>kbd]:rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-4",
variants: {
align: {
"inline-start":
"order-first ps-3 has-[>button]:ms-[-0.45rem] has-[>kbd]:ms-[-0.35rem]",
"inline-end":
"order-last pe-3 has-[>button]:me-[-0.45rem] has-[>kbd]:me-[-0.35rem]",
"inline-start": "order-first ps-3 has-[>button]:ms-[-0.45rem] has-[>kbd]:ms-[-0.35rem]",
"inline-end": "order-last pe-3 has-[>button]:me-[-0.45rem] has-[>kbd]:me-[-0.35rem]",
"block-start":
"order-first w-full justify-start px-3 pt-3 group-has-[>input]/input-group:pt-2.5 [.border-b]:pb-3",
"block-end":
"order-last w-full justify-start px-3 pb-3 group-has-[>input]/input-group:pb-2.5 [.border-t]:pt-3",
},
"block-end": "order-last w-full justify-start px-3 pb-3 group-has-[>input]/input-group:pb-2.5 [.border-t]:pt-3"
}
},
defaultVariants: {
align: "inline-start",
},
align: "inline-start"
}
});
export type InputGroupAddonAlign = VariantProps<typeof inputGroupAddonVariants>["align"];
@@ -8,12 +8,12 @@
xs: "h-6 gap-1 rounded-[calc(var(--radius)-5px)] px-2 has-[>svg]:px-2 [&>svg:not([class*='size-'])]:size-3.5",
sm: "h-8 gap-1.5 rounded-md px-2.5 has-[>svg]:px-2.5",
"icon-xs": "size-6 rounded-[calc(var(--radius)-5px)] p-0 has-[>svg]:p-0",
"icon-sm": "size-8 p-0 has-[>svg]:p-0",
},
"icon-sm": "size-8 p-0 has-[>svg]:p-0"
}
},
defaultVariants: {
size: "xs",
},
size: "xs"
}
});
export type InputGroupButtonSize = VariantProps<typeof inputGroupButtonVariants>["size"];
+1 -2
View File
@@ -5,8 +5,7 @@
type InputType = Exclude<HTMLInputTypeAttribute, "file">;
type Props = WithElementRef<
Omit<HTMLInputAttributes, "type"> &
({ type: "file"; files?: FileList } | { type?: InputType; files?: undefined })
Omit<HTMLInputAttributes, "type"> & ({ type: "file"; files?: FileList } | { type?: InputType; files?: undefined })
>;
let {
@@ -10,11 +10,6 @@
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script>
<div
bind:this={ref}
data-slot="item-actions"
class={cn("flex items-center gap-2", className)}
{...restProps}
>
<div bind:this={ref} data-slot="item-actions" class={cn("flex items-center gap-2", className)} {...restProps}>
{@render children?.()}
</div>
+4 -4
View File
@@ -7,12 +7,12 @@
variant: {
default: "bg-transparent",
icon: "bg-muted size-8 rounded-sm border [&_svg:not([class*='size-'])]:size-4",
image: "size-10 overflow-hidden rounded-sm [&_img]:size-full [&_img]:object-cover",
},
image: "size-10 overflow-hidden rounded-sm [&_img]:size-full [&_img]:object-cover"
}
},
defaultVariants: {
variant: "default",
},
variant: "default"
}
});
export type ItemMediaVariant = VariantProps<typeof itemMediaVariants>["variant"];
@@ -3,17 +3,7 @@
import { cn } from "$lib/utils.js";
import type { ComponentProps } from "svelte";
let {
ref = $bindable(null),
class: className,
...restProps
}: ComponentProps<typeof Separator> = $props();
let { ref = $bindable(null), class: className, ...restProps }: ComponentProps<typeof Separator> = $props();
</script>
<Separator
bind:ref
data-slot="item-separator"
orientation="horizontal"
class={cn("my-0", className)}
{...restProps}
/>
<Separator bind:ref data-slot="item-separator" orientation="horizontal" class={cn("my-0", className)} {...restProps} />
+6 -6
View File
@@ -7,17 +7,17 @@
variant: {
default: "bg-transparent",
outline: "border-border",
muted: "bg-muted/50",
muted: "bg-muted/50"
},
size: {
default: "gap-4 p-4",
sm: "gap-2.5 px-4 py-3",
},
sm: "gap-2.5 px-4 py-3"
}
},
defaultVariants: {
variant: "default",
size: "default",
},
size: "default"
}
});
export type ItemSize = VariantProps<typeof itemVariants>["size"];
@@ -47,7 +47,7 @@
"data-slot": "item",
"data-variant": variant,
"data-size": size,
...restProps,
...restProps
});
</script>
+1 -5
View File
@@ -2,11 +2,7 @@
import { Label as LabelPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
let {
ref = $bindable(null),
class: className,
...restProps
}: LabelPrimitive.RootProps = $props();
let { ref = $bindable(null), class: className, ...restProps }: LabelPrimitive.RootProps = $props();
</script>
<LabelPrimitive.Root
@@ -2,11 +2,7 @@
import { NavigationMenu as NavigationMenuPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
let {
ref = $bindable(null),
class: className,
...restProps
}: NavigationMenuPrimitive.ContentProps = $props();
let { ref = $bindable(null), class: className, ...restProps }: NavigationMenuPrimitive.ContentProps = $props();
</script>
<NavigationMenuPrimitive.Content
@@ -2,11 +2,7 @@
import { NavigationMenu as NavigationMenuPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
let {
ref = $bindable(null),
class: className,
...restProps
}: NavigationMenuPrimitive.IndicatorProps = $props();
let { ref = $bindable(null), class: className, ...restProps }: NavigationMenuPrimitive.IndicatorProps = $props();
</script>
<NavigationMenuPrimitive.Indicator
@@ -2,11 +2,7 @@
import { NavigationMenu as NavigationMenuPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
let {
ref = $bindable(null),
class: className,
...restProps
}: NavigationMenuPrimitive.ItemProps = $props();
let { ref = $bindable(null), class: className, ...restProps }: NavigationMenuPrimitive.ItemProps = $props();
</script>
<NavigationMenuPrimitive.Item
@@ -2,11 +2,7 @@
import { NavigationMenu as NavigationMenuPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
let {
ref = $bindable(null),
class: className,
...restProps
}: NavigationMenuPrimitive.LinkProps = $props();
let { ref = $bindable(null), class: className, ...restProps }: NavigationMenuPrimitive.LinkProps = $props();
</script>
<NavigationMenuPrimitive.Link
@@ -2,11 +2,7 @@
import { NavigationMenu as NavigationMenuPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
let {
ref = $bindable(null),
class: className,
...restProps
}: NavigationMenuPrimitive.ListProps = $props();
let { ref = $bindable(null), class: className, ...restProps }: NavigationMenuPrimitive.ListProps = $props();
</script>
<NavigationMenuPrimitive.List
@@ -2,11 +2,7 @@
import { NavigationMenu as NavigationMenuPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
let {
ref = $bindable(null),
class: className,
...restProps
}: NavigationMenuPrimitive.ViewportProps = $props();
let { ref = $bindable(null), class: className, ...restProps }: NavigationMenuPrimitive.ViewportProps = $props();
</script>
<div class={cn("absolute start-0 top-full isolate z-50 flex justify-center")}>
@@ -18,10 +18,7 @@
bind:ref
data-slot="navigation-menu"
data-viewport={viewport}
class={cn(
"group/navigation-menu relative flex max-w-max flex-1 items-center justify-center",
className
)}
class={cn("group/navigation-menu relative flex max-w-max flex-1 items-center justify-center", className)}
{...restProps}
>
{@render children?.()}
@@ -2,16 +2,7 @@
import { cn } from "$lib/utils.js";
import { Popover as PopoverPrimitive } from "bits-ui";
let {
ref = $bindable(null),
class: className,
...restProps
}: PopoverPrimitive.TriggerProps = $props();
let { ref = $bindable(null), class: className, ...restProps }: PopoverPrimitive.TriggerProps = $props();
</script>
<PopoverPrimitive.Trigger
bind:ref
data-slot="popover-trigger"
class={cn("", className)}
{...restProps}
/>
<PopoverPrimitive.Trigger bind:ref data-slot="popover-trigger" class={cn("", className)} {...restProps} />
@@ -22,9 +22,7 @@
{#snippet children({ checked })}
<div data-slot="radio-group-indicator" class="relative flex items-center justify-center">
{#if checked}
<CircleIcon
class="fill-primary absolute start-1/2 top-1/2 size-2 -translate-x-1/2 -translate-y-1/2"
/>
<CircleIcon class="fill-primary absolute start-1/2 top-1/2 size-2 -translate-x-1/2 -translate-y-1/2" />
{/if}
</div>
{/snippet}
@@ -34,9 +34,7 @@
>
<SelectScrollUpButton />
<SelectPrimitive.Viewport
class={cn(
"h-(--bits-select-anchor-height) w-full min-w-(--bits-select-anchor-width) scroll-my-1 p-1"
)}
class={cn("h-(--bits-select-anchor-height) w-full min-w-(--bits-select-anchor-width) scroll-my-1 p-1")}
>
{@render children?.()}
</SelectPrimitive.Viewport>
@@ -3,11 +3,7 @@
import { Separator } from "$lib/components/ui/separator/index.js";
import { cn } from "$lib/utils.js";
let {
ref = $bindable(null),
class: className,
...restProps
}: SeparatorPrimitive.RootProps = $props();
let { ref = $bindable(null), class: className, ...restProps }: SeparatorPrimitive.RootProps = $props();
</script>
<Separator
+1 -5
View File
@@ -1,11 +1,7 @@
<script lang="ts">
import { Select as SelectPrimitive } from "bits-ui";
let {
open = $bindable(false),
value = $bindable(),
...restProps
}: SelectPrimitive.RootProps = $props();
let { open = $bindable(false), value = $bindable(), ...restProps }: SelectPrimitive.RootProps = $props();
</script>
<SelectPrimitive.Root bind:open bind:value={value as never} {...restProps} />
@@ -5,14 +5,16 @@
variants: {
side: {
top: "data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top inset-x-0 top-0 h-auto border-b",
bottom: "data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom inset-x-0 bottom-0 h-auto border-t",
bottom:
"data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom inset-x-0 bottom-0 h-auto border-t",
left: "data-[state=closed]:slide-out-to-start data-[state=open]:slide-in-from-start inset-y-0 start-0 h-full w-3/4 border-e sm:max-w-sm",
right: "data-[state=closed]:slide-out-to-end data-[state=open]:slide-in-from-end inset-y-0 end-0 h-full w-3/4 border-s sm:max-w-sm",
},
right:
"data-[state=closed]:slide-out-to-end data-[state=open]:slide-in-from-end inset-y-0 end-0 h-full w-3/4 border-s sm:max-w-sm"
}
},
defaultVariants: {
side: "right",
},
side: "right"
}
});
export type Side = VariantProps<typeof sheetVariants>["side"];
@@ -2,11 +2,7 @@
import { Dialog as SheetPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
let {
ref = $bindable(null),
class: className,
...restProps
}: SheetPrimitive.DescriptionProps = $props();
let { ref = $bindable(null), class: className, ...restProps }: SheetPrimitive.DescriptionProps = $props();
</script>
<SheetPrimitive.Description
@@ -10,11 +10,6 @@
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script>
<div
bind:this={ref}
data-slot="sheet-footer"
class={cn("mt-auto flex flex-col gap-2 p-4", className)}
{...restProps}
>
<div bind:this={ref} data-slot="sheet-footer" class={cn("mt-auto flex flex-col gap-2 p-4", className)} {...restProps}>
{@render children?.()}
</div>
@@ -10,11 +10,6 @@
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script>
<div
bind:this={ref}
data-slot="sheet-header"
class={cn("flex flex-col gap-1.5 p-4", className)}
{...restProps}
>
<div bind:this={ref} data-slot="sheet-header" class={cn("flex flex-col gap-1.5 p-4", className)} {...restProps}>
{@render children?.()}
</div>
@@ -2,11 +2,7 @@
import { Dialog as SheetPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
let {
ref = $bindable(null),
class: className,
...restProps
}: SheetPrimitive.OverlayProps = $props();
let { ref = $bindable(null), class: className, ...restProps }: SheetPrimitive.OverlayProps = $props();
</script>
<SheetPrimitive.Overlay
@@ -2,11 +2,7 @@
import { Dialog as SheetPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
let {
ref = $bindable(null),
class: className,
...restProps
}: SheetPrimitive.TitleProps = $props();
let { ref = $bindable(null), class: className, ...restProps }: SheetPrimitive.TitleProps = $props();
</script>
<SheetPrimitive.Title
@@ -53,9 +53,7 @@ class SidebarState {
};
toggle = () => {
return this.#isMobile.current
? (this.openMobile = !this.openMobile)
: this.setOpen(!this.open);
return this.#isMobile.current ? (this.openMobile = !this.openMobile) : this.setOpen(!this.open);
};
}
@@ -23,7 +23,7 @@
),
"data-slot": "sidebar-group-action",
"data-sidebar": "group-action",
...restProps,
...restProps
});
</script>
@@ -21,7 +21,7 @@
),
"data-slot": "sidebar-group-label",
"data-sidebar": "group-label",
...restProps,
...restProps
});
</script>
@@ -30,7 +30,7 @@
),
"data-slot": "sidebar-menu-action",
"data-sidebar": "menu-action",
...restProps,
...restProps
});
</script>
@@ -7,23 +7,21 @@
variant: {
default: "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
outline:
"bg-background hover:bg-sidebar-accent hover:text-sidebar-accent-foreground shadow-[0_0_0_1px_var(--sidebar-border)] hover:shadow-[0_0_0_1px_var(--sidebar-accent)]",
"bg-background hover:bg-sidebar-accent hover:text-sidebar-accent-foreground shadow-[0_0_0_1px_var(--sidebar-border)] hover:shadow-[0_0_0_1px_var(--sidebar-accent)]"
},
size: {
default: "h-8 text-sm",
sm: "h-7 text-xs",
lg: "h-12 text-sm group-data-[collapsible=icon]:p-0!",
},
lg: "h-12 text-sm group-data-[collapsible=icon]:p-0!"
}
},
defaultVariants: {
variant: "default",
size: "default",
},
size: "default"
}
});
export type SidebarMenuButtonVariant = VariantProps<
typeof sidebarMenuButtonVariants
>["variant"];
export type SidebarMenuButtonVariant = VariantProps<typeof sidebarMenuButtonVariants>["variant"];
export type SidebarMenuButtonSize = VariantProps<typeof sidebarMenuButtonVariants>["size"];
</script>
@@ -63,7 +61,7 @@
"data-sidebar": "menu-button",
"data-size": size,
"data-active": isActive,
...restProps,
...restProps
});
</script>
@@ -30,7 +30,7 @@
"data-sidebar": "menu-sub-button",
"data-size": size,
"data-active": isActive,
...restProps,
...restProps
});
</script>
@@ -2,12 +2,7 @@
import * as Tooltip from "$lib/components/ui/tooltip/index.js";
import { cn, type WithElementRef } from "$lib/utils.js";
import type { HTMLAttributes } from "svelte/elements";
import {
SIDEBAR_COOKIE_MAX_AGE,
SIDEBAR_COOKIE_NAME,
SIDEBAR_WIDTH,
SIDEBAR_WIDTH_ICON,
} from "./constants.js";
import { SIDEBAR_COOKIE_MAX_AGE, SIDEBAR_COOKIE_NAME, SIDEBAR_WIDTH, SIDEBAR_WIDTH_ICON } from "./constants.js";
import { setSidebar } from "./context.svelte.js";
let {
@@ -31,7 +26,7 @@
// This sets the cookie to keep the sidebar state.
document.cookie = `${SIDEBAR_COOKIE_NAME}=${open}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;
},
}
});
</script>
@@ -41,10 +36,7 @@
<div
data-slot="sidebar-wrapper"
style="--sidebar-width: {SIDEBAR_WIDTH}; --sidebar-width-icon: {SIDEBAR_WIDTH_ICON}; {style}"
class={cn(
"group/sidebar-wrapper has-data-[variant=inset]:bg-sidebar flex min-h-svh w-full",
className
)}
class={cn("group/sidebar-wrapper has-data-[variant=inset]:bg-sidebar flex min-h-svh w-full", className)}
bind:this={ref}
{...restProps}
>
@@ -3,11 +3,7 @@
import { cn } from "$lib/utils.js";
import type { ComponentProps } from "svelte";
let {
ref = $bindable(null),
class: className,
...restProps
}: ComponentProps<typeof Separator> = $props();
let { ref = $bindable(null), class: className, ...restProps }: ComponentProps<typeof Separator> = $props();
</script>
<Separator
+2 -8
View File
@@ -24,20 +24,14 @@
{#if collapsible === "none"}
<div
class={cn(
"bg-sidebar text-sidebar-foreground flex h-full w-(--sidebar-width) flex-col",
className
)}
class={cn("bg-sidebar text-sidebar-foreground flex h-full w-(--sidebar-width) flex-col", className)}
bind:this={ref}
{...restProps}
>
{@render children?.()}
</div>
{:else if sidebar.isMobile}
<Sheet.Root
bind:open={() => sidebar.openMobile, (v) => sidebar.setOpenMobile(v)}
{...restProps}
>
<Sheet.Root bind:open={() => sidebar.openMobile, (v) => sidebar.setOpenMobile(v)} {...restProps}>
<Sheet.Content
data-sidebar="sidebar"
data-slot="sidebar"
+1 -6
View File
@@ -6,9 +6,4 @@
let { class: className, ...restProps }: ComponentProps<typeof Loader2Icon> = $props();
</script>
<Loader2Icon
role="status"
aria-label="Loading"
class={cn("size-4 animate-spin", className)}
{...restProps}
/>
<Loader2Icon role="status" aria-label="Loading" class={cn("size-4 animate-spin", className)} {...restProps} />
@@ -10,11 +10,6 @@
}: WithElementRef<HTMLAttributes<HTMLTableSectionElement>> = $props();
</script>
<tbody
bind:this={ref}
data-slot="table-body"
class={cn("[&_tr:last-child]:border-0", className)}
{...restProps}
>
<tbody bind:this={ref} data-slot="table-body" class={cn("[&_tr:last-child]:border-0", className)} {...restProps}>
{@render children?.()}
</tbody>
+2 -10
View File
@@ -2,21 +2,13 @@
import { cn, type WithElementRef } from "$lib/utils.js";
import type { HTMLTdAttributes } from "svelte/elements";
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLTdAttributes> = $props();
let { ref = $bindable(null), class: className, children, ...restProps }: WithElementRef<HTMLTdAttributes> = $props();
</script>
<td
bind:this={ref}
data-slot="table-cell"
class={cn(
"bg-clip-padding p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pe-0",
className
)}
class={cn("bg-clip-padding p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pe-0", className)}
{...restProps}
>
{@render children?.()}
@@ -2,12 +2,7 @@
import { cn, type WithElementRef } from "$lib/utils.js";
import type { HTMLThAttributes } from "svelte/elements";
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLThAttributes> = $props();
let { ref = $bindable(null), class: className, children, ...restProps }: WithElementRef<HTMLThAttributes> = $props();
</script>
<th
@@ -10,11 +10,6 @@
}: WithElementRef<HTMLAttributes<HTMLTableSectionElement>> = $props();
</script>
<thead
bind:this={ref}
data-slot="table-header"
class={cn("[&_tr]:border-b", className)}
{...restProps}
>
<thead bind:this={ref} data-slot="table-header" class={cn("[&_tr]:border-b", className)} {...restProps}>
{@render children?.()}
</thead>
+1 -6
View File
@@ -11,12 +11,7 @@
</script>
<div data-slot="table-container" class="relative w-full overflow-x-auto">
<table
bind:this={ref}
data-slot="table"
class={cn("w-full caption-bottom text-sm", className)}
{...restProps}
>
<table bind:this={ref} data-slot="table" class={cn("w-full caption-bottom text-sm", className)} {...restProps}>
{@render children?.()}
</table>
</div>
+2 -11
View File
@@ -2,16 +2,7 @@
import { Tabs as TabsPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
let {
ref = $bindable(null),
class: className,
...restProps
}: TabsPrimitive.ContentProps = $props();
let { ref = $bindable(null), class: className, ...restProps }: TabsPrimitive.ContentProps = $props();
</script>
<TabsPrimitive.Content
bind:ref
data-slot="tabs-content"
class={cn("flex-1 outline-none", className)}
{...restProps}
/>
<TabsPrimitive.Content bind:ref data-slot="tabs-content" class={cn("flex-1 outline-none", className)} {...restProps} />
+1 -5
View File
@@ -2,11 +2,7 @@
import { Tabs as TabsPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
let {
ref = $bindable(null),
class: className,
...restProps
}: TabsPrimitive.ListProps = $props();
let { ref = $bindable(null), class: className, ...restProps }: TabsPrimitive.ListProps = $props();
</script>
<TabsPrimitive.List
@@ -2,11 +2,7 @@
import { Tabs as TabsPrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
let {
ref = $bindable(null),
class: className,
...restProps
}: TabsPrimitive.TriggerProps = $props();
let { ref = $bindable(null), class: className, ...restProps }: TabsPrimitive.TriggerProps = $props();
</script>
<TabsPrimitive.Trigger
+1 -7
View File
@@ -10,10 +10,4 @@
}: TabsPrimitive.RootProps = $props();
</script>
<TabsPrimitive.Root
bind:ref
bind:value
data-slot="tabs"
class={cn("flex flex-col gap-2", className)}
{...restProps}
/>
<TabsPrimitive.Root bind:ref bind:value data-slot="tabs" class={cn("flex flex-col gap-2", className)} {...restProps} />
+13
View File
@@ -0,0 +1,13 @@
import Root from "./toggle.svelte";
export {
toggleVariants,
type ToggleSize,
type ToggleVariant,
type ToggleVariants,
} from "./toggle.svelte";
export {
Root,
//
Root as Toggle,
};
@@ -0,0 +1,52 @@
<script lang="ts" module>
import { type VariantProps, tv } from "tailwind-variants";
export const toggleVariants = tv({
base: "hover:bg-muted hover:text-muted-foreground data-[state=on]:bg-accent data-[state=on]:text-accent-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
variants: {
variant: {
default: "bg-transparent",
outline:
"border-input hover:bg-accent hover:text-accent-foreground border bg-transparent shadow-xs",
},
size: {
default: "h-9 min-w-9 px-2",
sm: "h-8 min-w-8 px-1.5",
lg: "h-10 min-w-10 px-2.5",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
});
export type ToggleVariant = VariantProps<typeof toggleVariants>["variant"];
export type ToggleSize = VariantProps<typeof toggleVariants>["size"];
export type ToggleVariants = VariantProps<typeof toggleVariants>;
</script>
<script lang="ts">
import { Toggle as TogglePrimitive } from "bits-ui";
import { cn } from "$lib/utils.js";
let {
ref = $bindable(null),
pressed = $bindable(false),
class: className,
size = "default",
variant = "default",
...restProps
}: TogglePrimitive.RootProps & {
variant?: ToggleVariant;
size?: ToggleSize;
} = $props();
</script>
<TogglePrimitive.Root
bind:ref
bind:pressed
data-slot="toggle"
class={cn(toggleVariants({ variant, size }), className)}
{...restProps}
/>
-1
View File
@@ -90,4 +90,3 @@
"We have sent a code to your email. Please enter it below to confirm your login": "Abbiamo inviato un codice alla tua email. Inseriscilo qui sotto per confermare l'accesso",
"You are logged in as %email": "Sei connesso come %email"
}
+19 -2
View File
@@ -1,3 +1,18 @@
const kenerAPITypeData = {
url: "https://kener.ing",
method: "GET",
headers: [],
body: "",
timeout: 10000,
eval: "(async function (statusCode, responseTime, responseRaw, modules) { \n\tlet statusCodeShort = Math.floor(statusCode/100);\n if(statusCode == 429 || (statusCodeShort >=2 && statusCodeShort <= 3)) {\n return {\n\t\t\tstatus: 'UP',\n\t\t\tlatency: responseTime,\n }\n } \n\treturn {\n\t\tstatus: 'DOWN',\n\t\tlatency: responseTime,\n\t}\n})",
allowSelfSignedCert: false,
};
const defaultMonitorSettings = {
uptime_formula_numerator: "up + maintenance",
uptime_formula_denominator: "up + maintenance + down + degraded",
};
let seedMonitorData = [
{
tag: "earth",
@@ -15,6 +30,8 @@ let seedMonitorData = [
day_degraded_minimum_count: 1,
day_down_minimum_count: 1,
include_degraded_in_downtime: "NO",
is_hidden: "NO",
monitor_settings_json: JSON.stringify(defaultMonitorSettings),
},
{
tag: "kener",
@@ -29,12 +46,12 @@ let seedMonitorData = [
monitor_type: "API",
down_trigger: null,
degraded_trigger: null,
type_data: `{"url":"https://kener.ing","method":"GET","headers":[],"body":"","timeout":10000,"eval":"(async function (statusCode, responseTime, responseRaw, modules) {\n\tlet statusCodeShort = Math.floor(statusCode/100);\n if(statusCode == 429 || (statusCodeShort >=2 && statusCodeShort <= 3)) {\n return {\n\t\t\tstatus: 'UP',\n\t\t\tlatency: responseTime,\n }\n } \n\treturn {\n\t\tstatus: 'DOWN',\n\t\tlatency: responseTime,\n\t}\n})","allowSelfSignedCert":false}`,
type_data: JSON.stringify(kenerAPITypeData),
day_degraded_minimum_count: 1,
day_down_minimum_count: 1,
include_degraded_in_downtime: "NO",
is_hidden: "NO",
monitor_setting_json: `{"uptime_formula_numerator":"up + maintenance","uptime_formula_denominator":"up + maintenance + down + degraded"}`,
monitor_settings_json: JSON.stringify(defaultMonitorSettings),
},
];
+6 -8
View File
@@ -5,13 +5,7 @@ import * as cheerio from "cheerio";
import { DefaultAPIEval } from "../../anywhere.js";
import version from "../../version.js";
import https from "https";
import type { ApiMonitor, EvalResponse } from "../types/monitor.js";
interface MonitoringResult {
status: string;
latency: number;
type: string;
}
import type { ApiMonitor, EvalResponse, MonitoringResult } from "../types/monitor.js";
class ApiCall {
monitor: ApiMonitor;
@@ -99,6 +93,7 @@ class ApiCall {
}
let statusCode = 500;
let latency = 0;
let errorMessage = "";
let resp = "";
let timeoutError = false;
const start = Date.now();
@@ -113,10 +108,11 @@ class ApiCall {
response?: { status?: number; data?: string };
};
console.log(`Error in apiCall ${tag}`, error.message);
errorMessage = error.message || "Unknown error";
// Better timeout detection
if (error.code === "ECONNABORTED" || (error.message && error.message.includes("timeout"))) {
timeoutError = true;
errorMessage = "Request timed out";
console.log(`Timeout in api call for ${tag} at ${Math.floor(Date.now() / 1000)}`);
}
@@ -149,6 +145,7 @@ class ApiCall {
);
evalResp = await evalFunction(statusCode, latency, resp, modules);
} catch (error: unknown) {
errorMessage += ` | Eval error: ${(error as Error).message}`;
console.log(`Error in monitorEval for ${tag}`, (error as Error).message);
}
@@ -176,6 +173,7 @@ class ApiCall {
status: DOWN,
latency: latency,
type: ERROR,
error_message: errorMessage,
};
if (evalResp.status !== undefined && evalResp.status !== null) {
toWrite.status = evalResp.status;
+1
View File
@@ -4,6 +4,7 @@ export interface MonitoringResult {
status: string;
latency: number;
type: string;
error_message?: string;
}
export interface MonitoringResultTS {
+1 -1
View File
@@ -22,7 +22,7 @@ export const load: LayoutServerLoad = async ({ cookies, request, url }) => {
let isSetupComplete = await IsSetupComplete();
if (!isSetupComplete) {
throw redirect(302, resolve(`/manage/setup`));
throw redirect(302, resolve(`/account/signin`));
}
let isLoggedIn = await IsLoggedInSession(cookies);
+10
View File
@@ -2,6 +2,8 @@ import i18n from "$lib/i18n/server";
import { redirect } from "@sveltejs/kit";
import MobileDetect from "mobile-detect";
import type { LayoutServerLoad } from "./$types";
import { IsEmailSetup, CheckInvitationExists } from "$lib/server/controllers/controller.js";
import { INVITE_VERIFY_EMAIL } from "$lib/server/constants.js";
import { resolve } from "$app/paths";
import {
@@ -23,6 +25,11 @@ export const load: LayoutServerLoad = async ({ cookies, request, url }) => {
let isLoggedIn = await IsLoggedInSession(cookies);
//if user not set throw redirect to signin
if (!isLoggedIn.user) {
throw redirect(302, "/account/logout");
}
const siteData = await GetAllSiteData();
let localTz = "UTC";
const localTzCookie = cookies.get("localTz");
@@ -42,6 +49,9 @@ export const load: LayoutServerLoad = async ({ cookies, request, url }) => {
// const emailSubscriptionTrigger = await GetSubscriptionTriggerByEmail();
return {
userDb: isLoggedIn.user,
siteStatusColors,
canSendEmail: IsEmailSetup(),
activeInvitationExists: await CheckInvitationExists(isLoggedIn.user.id, INVITE_VERIFY_EMAIL),
};
};
@@ -29,7 +29,7 @@
import { ValidateIpAddress, IsValidHost, IsValidNameServer, IsValidURL, IsValidPort } from "$lib/clientTools";
import { GAMEDIG_SOCKET_TIMEOUT } from "$lib/anywhere";
import * as InputGroup from "$lib/components/ui/input-group/index.js";
import type { MonitoringResult } from "$lib/server/types/monitor.js";
// Type-specific components
import {
MonitorApi,
@@ -59,7 +59,7 @@
// Test monitor state
let testingMonitor = $state(false);
let testResult = $state<{ status?: string; latency?: number; error?: string } | null>(null);
let testResult = $state<MonitoringResult | null>(null);
// Modify monitoring data state
let modifyingData = $state(false);
@@ -311,7 +311,8 @@
if (m.type_data) {
try {
typeData = JSON.parse(m.type_data);
} catch {
} catch (e: any) {
console.error("Failed to parse type_data:", e);
typeData = {};
}
}
@@ -323,7 +324,8 @@
uptime_formula_numerator: settings.uptime_formula_numerator || "up + maintenance",
uptime_formula_denominator: settings.uptime_formula_denominator || "up + maintenance + down + degraded"
};
} catch {
} catch (e: any) {
console.error("Failed to parse monitor_settings_json:", e);
// Keep defaults
}
}
@@ -601,7 +603,7 @@
const result = await response.json();
testResult = result;
} catch (e) {
testResult = { error: "Failed to test monitor" };
testResult = { error_message: "Failed to test monitor", status: "NO_DATA", latency: 0, type: "error" };
} finally {
testingMonitor = false;
}
@@ -944,7 +946,73 @@
{/if}
</div>
</Card.Content>
<Card.Footer class="flex justify-end">
<Card.Footer class="flex justify-between gap-2">
<Dialog.Root
onOpenChange={(e) => {
if (e) testMonitor();
}}
>
<Dialog.Trigger>
{#snippet child({ props })}
<Button {...props} variant="secondary">
<PlayIcon class="size-4" />
Test Monitor
</Button>
{/snippet}
</Dialog.Trigger>
<Dialog.Content>
<Dialog.Header>
<Dialog.Title>
{#if testingMonitor}
Running Test
{:else if testResult}
Test Result
{:else}
Test Monitor
{/if}
</Dialog.Title>
</Dialog.Header>
<div class="flex flex-col justify-center gap-2">
{#if testingMonitor}
<div class="flex flex-col items-center gap-2 py-8">
<Loader class="size-8 animate-spin" />
<p class="text-muted-foreground mt-4 text-center">
Please wait while the test is being performed...
</p>
</div>
{:else if testResult}
<div class="mt-4 flex flex-col gap-4">
{#if testResult.error_message}
<div class="bg-destructive/10 text-destructive rounded-md p-3 text-sm font-medium">
{testResult.error_message}
</div>
{/if}
<div class="grid grid-cols-2 gap-4">
<div class="rounded-lg border p-4 text-center">
<div class="text-muted-foreground text-xs uppercase">Status</div>
<div class="mt-1 text-2xl font-bold text-{testResult.status.toLowerCase()}">
{testResult.status}
</div>
</div>
<div class="rounded-lg border p-4 text-center">
<div class="text-muted-foreground text-xs uppercase">Response Time</div>
<div class="mt-1 text-2xl font-bold">
{testResult.latency}ms
</div>
</div>
</div>
<div class="flex justify-end">
<Button variant="outline" size="sm" onclick={testMonitor} disabled={testingMonitor}>
<PlayIcon class="mr-2 size-3" />
Run Test Again
</Button>
</div>
</div>
{/if}
</div>
</Dialog.Content>
</Dialog.Root>
<Button onclick={saveTypeSettings} disabled={savingType || !isTypeSettingsValid}>
{#if savingType}
<Loader class="mr-2 size-4 animate-spin" />
@@ -1045,59 +1113,6 @@
</Card.Root>
{/if}
<!-- Test Monitor Card -->
{#if !isNew && monitor.monitor_type && monitor.monitor_type !== "NONE"}
<Card.Root>
<Card.Header>
<Card.Title class="flex items-center gap-2">
<PlayIcon class="size-5" />
Test Monitor
</Card.Title>
<Card.Description>Run a test to check the current status of this monitor</Card.Description>
</Card.Header>
<Card.Content>
<div class="flex items-center gap-4">
<Button onclick={testMonitor} disabled={testingMonitor}>
{#if testingMonitor}
<Loader class="mr-2 size-4 animate-spin" />
Testing...
{:else}
<PlayIcon class="mr-2 size-4" />
Run Test
{/if}
</Button>
{#if testResult}
<div class="flex-1 rounded-md border p-3">
{#if testResult.error}
<p class="text-destructive text-sm">{testResult.error}</p>
{:else if testResult.status !== undefined && testResult.latency !== undefined}
<div class="flex items-center gap-4">
<div>
<p class="text-muted-foreground text-xs">Status</p>
<p
class="font-medium"
class:text-green-500={testResult.status === "UP"}
class:text-yellow-500={testResult.status === "DEGRADED"}
class:text-red-500={testResult.status === "DOWN"}
>
{testResult.status}
</p>
</div>
<div>
<p class="text-muted-foreground text-xs">Response Time</p>
<p class="font-medium">{testResult.latency}ms</p>
</div>
</div>
{:else}
<p class="text-muted-foreground text-sm">No result available</p>
{/if}
</div>
{/if}
</div>
</Card.Content>
</Card.Root>
{/if}
<!-- Modify Monitoring Data Card -->
{#if !isNew}
<Card.Root>
+251 -18
View File
@@ -1,14 +1,138 @@
<script lang="ts">
import CreditCardIcon from "@lucide/svelte/icons/credit-card";
import DotsVerticalIcon from "@lucide/svelte/icons/camera";
import LogoutIcon from "@lucide/svelte/icons/home";
import DotsVerticalIcon from "@lucide/svelte/icons/ellipsis-vertical";
import LogoutIcon from "@lucide/svelte/icons/log-out";
import NotificationIcon from "@lucide/svelte/icons/bell";
import UserCircleIcon from "@lucide/svelte/icons/user-circle";
import CheckIcon from "@lucide/svelte/icons/check";
import LoaderIcon from "@lucide/svelte/icons/loader";
import * as Avatar from "$lib/components/ui/avatar/index.js";
import * as DropdownMenu from "$lib/components/ui/dropdown-menu/index.js";
import * as Sidebar from "$lib/components/ui/sidebar/index.js";
let { user }: { user: { name: string; email: string; avatar: string } } = $props();
import * as Dialog from "$lib/components/ui/dialog/index.js";
import { Input } from "$lib/components/ui/input/index.js";
import { Label } from "$lib/components/ui/label/index.js";
import { Button } from "$lib/components/ui/button/index.js";
import { page } from "$app/state";
import { base } from "$app/paths";
import type { UserRecordPublic } from "$lib/server/types/db";
let user = $state<UserRecordPublic>(page.data.userDb);
let nameAbbr = $derived(
user.name
.split(" ")
.map((n) => n[0])
.join("")
.slice(0, 2)
.toUpperCase()
);
const sidebar = Sidebar.useSidebar();
// Account dialog state
let accountDialogOpen = $state(false);
let myName = $state(user.name);
let myPassword = $state("");
let plainPassword = $state("");
let savingName = $state(false);
let resettingPass = $state(false);
let nameError = $state("");
let passwordError = $state("");
let nameSuccess = $state(false);
let passwordSuccess = $state(false);
// Password validation
let hasDigit = $derived(/\d/.test(myPassword));
let hasLowercase = $derived(/[a-z]/.test(myPassword));
let hasUppercase = $derived(/[A-Z]/.test(myPassword));
let hasLetter = $derived(/[a-zA-Z]/.test(myPassword));
let hasMinLength = $derived(myPassword.length >= 8);
let passwordsMatch = $derived(myPassword === plainPassword && myPassword !== "");
let isPasswordValid = $derived(
hasDigit && hasLowercase && hasUppercase && hasLetter && hasMinLength && passwordsMatch
);
// Role badge styling
let roleBadgeClass = $derived(() => {
switch (user.role) {
case "admin":
return "bg-pink-100 text-pink-800 dark:bg-pink-900 dark:text-pink-300";
case "editor":
return "bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-300";
case "member":
return "bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-300";
default:
return "bg-gray-100 text-gray-800 dark:bg-gray-900 dark:text-gray-300";
}
});
async function saveName() {
savingName = true;
nameError = "";
nameSuccess = false;
try {
const response = await fetch(base + "/manage/api", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
action: "updateUser",
data: { updateValue: myName, updateKey: "name" }
})
});
const resp = await response.json();
if (resp.error) {
nameError = resp.error;
} else {
user.name = myName;
nameSuccess = true;
setTimeout(() => (nameSuccess = false), 2000);
}
} catch {
nameError = "Error while saving name";
} finally {
savingName = false;
}
}
async function updatePassword() {
resettingPass = true;
passwordError = "";
passwordSuccess = false;
try {
const response = await fetch(base + "/manage/api", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
action: "updatePassword",
data: { newPassword: myPassword, newPlainPassword: plainPassword }
})
});
const resp = await response.json();
if (resp.error) {
passwordError = resp.error;
} else {
myPassword = "";
plainPassword = "";
passwordSuccess = true;
setTimeout(() => (passwordSuccess = false), 2000);
}
} catch {
passwordError = "Error while updating password";
} finally {
resettingPass = false;
}
}
function openAccountDialog() {
myName = user.name;
myPassword = "";
plainPassword = "";
nameError = "";
passwordError = "";
nameSuccess = false;
passwordSuccess = false;
accountDialogOpen = true;
}
</script>
<Sidebar.Menu>
@@ -22,11 +146,12 @@
class="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
>
<Avatar.Root class="size-8 rounded-lg grayscale">
<Avatar.Image src={user.avatar} alt={user.name} />
<Avatar.Fallback class="rounded-lg">CN</Avatar.Fallback>
<Avatar.Fallback class="rounded-lg">
{nameAbbr}
</Avatar.Fallback>
</Avatar.Root>
<div class="grid flex-1 text-start text-sm leading-tight">
<span class="truncate font-medium">{user.name}</span>
<span class="truncate font-medium">{nameAbbr}</span>
<span class="text-muted-foreground truncate text-xs">
{user.email}
</span>
@@ -44,8 +169,7 @@
<DropdownMenu.Label class="p-0 font-normal">
<div class="flex items-center gap-2 px-1 py-1.5 text-start text-sm">
<Avatar.Root class="size-8 rounded-lg">
<Avatar.Image src={user.avatar} alt={user.name} />
<Avatar.Fallback class="rounded-lg">CN</Avatar.Fallback>
<Avatar.Fallback class="rounded-lg">{nameAbbr}</Avatar.Fallback>
</Avatar.Root>
<div class="grid flex-1 text-start text-sm leading-tight">
<span class="truncate font-medium">{user.name}</span>
@@ -57,25 +181,134 @@
</DropdownMenu.Label>
<DropdownMenu.Separator />
<DropdownMenu.Group>
<DropdownMenu.Item>
<DropdownMenu.Item onclick={openAccountDialog}>
<UserCircleIcon />
Account
</DropdownMenu.Item>
<DropdownMenu.Item>
<CreditCardIcon />
Billing
</DropdownMenu.Item>
<DropdownMenu.Item>
<NotificationIcon />
Notifications
</DropdownMenu.Item>
</DropdownMenu.Group>
<DropdownMenu.Separator />
<DropdownMenu.Item>
<DropdownMenu.Item onSelect={() => (window.location.href = `${base}/account/logout`)}>
{#snippet child({ props })}
<Button {...props} variant="ghost" href="/account/logout" class="w-full justify-start">
<LogoutIcon />
Log out
</Button>
{/snippet}
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
</Sidebar.MenuItem>
</Sidebar.Menu>
<Dialog.Root bind:open={accountDialogOpen}>
<Dialog.Content class="max-w-md">
<Dialog.Header>
<Dialog.Title class="flex flex-col justify-between">
<span>Account Settings</span>
</Dialog.Title>
<Dialog.Description class="flex flex-col gap-2">
<span> Manage your profile information. </span>
<div class="flex items-center justify-between">
<span class="text-foreground rounded-sm font-medium">
{user.email}
</span>
<span class="text-foreground rounded-sm font-medium uppercase">
{user.role}
</span>
</div>
</Dialog.Description>
</Dialog.Header>
<div class="flex flex-col gap-6 py-4">
<!-- Name Section -->
<form
class="flex flex-col gap-3"
onsubmit={(e) => {
e.preventDefault();
saveName();
}}
>
<Label for="account-name">Name</Label>
<div class="flex gap-2">
<Input id="account-name" bind:value={myName} placeholder="Your name" disabled={savingName} class="flex-1" />
<Button type="submit" disabled={savingName || !myName.trim()}>
{#if savingName}
<LoaderIcon class="size-4 animate-spin" />
{:else if nameSuccess}
<CheckIcon class="size-4" />
{:else}
Save
{/if}
</Button>
</div>
{#if nameError}
<p class="text-destructive text-sm">{nameError}</p>
{/if}
</form>
<hr />
<!-- Password Section -->
<form
class="flex flex-col gap-3"
onsubmit={(e) => {
e.preventDefault();
updatePassword();
}}
>
<Label for="new-password">Change Password</Label>
<Input
id="new-password"
type="password"
bind:value={myPassword}
placeholder="New Password"
disabled={resettingPass}
/>
<Input
id="confirm-password"
type="password"
bind:value={plainPassword}
placeholder="Confirm Password"
disabled={resettingPass}
/>
<div class="text-muted-foreground text-xs">
<p class="mb-1 font-medium">Password requirements:</p>
<ul class="grid grid-cols-2 gap-1">
<li class:text-green-500={hasDigit}>
{#if hasDigit}<CheckIcon class="inline size-3" />{/if} One digit
</li>
<li class:text-green-500={hasLowercase}>
{#if hasLowercase}<CheckIcon class="inline size-3" />{/if} One lowercase
</li>
<li class:text-green-500={hasUppercase}>
{#if hasUppercase}<CheckIcon class="inline size-3" />{/if} One uppercase
</li>
<li class:text-green-500={hasMinLength}>
{#if hasMinLength}<CheckIcon class="inline size-3" />{/if} 8+ characters
</li>
<li class:text-green-500={passwordsMatch}>
{#if passwordsMatch}<CheckIcon class="inline size-3" />{/if} Passwords match
</li>
</ul>
</div>
<Button type="submit" disabled={resettingPass || !isPasswordValid}>
{#if resettingPass}
<LoaderIcon class="mr-2 size-4 animate-spin" />
Updating...
{:else if passwordSuccess}
<CheckIcon class="mr-2 size-4" />
Updated!
{:else}
Update Password
{/if}
</Button>
{#if passwordError}
<p class="text-destructive text-sm">{passwordError}</p>
{/if}
</form>
</div>
</Dialog.Content>
</Dialog.Root>
+1 -1
View File
@@ -53,7 +53,7 @@ main {
}
.note {
@apply mt-4 rounded-md border bg-background p-3 text-sm shadow-sm;
@apply bg-background mt-4 rounded-md border p-3 text-sm shadow-sm;
}
.note.danger {
border: 1px solid #e3342f;