mirror of
https://github.com/rajnandan1/kener.git
synced 2026-06-23 04:10:22 +00:00
Remove unused llms.txt and sitemap.xml server handlers to streamline codebase
This commit is contained in:
@@ -81,7 +81,7 @@ SECRET_PARAM=your_real_secret_value
|
||||
## Cheerio HTML content check {#cheerio-html-check-eval}
|
||||
|
||||
```javascript
|
||||
;(async function (statusCode, responseTime, responseRaw, modules) {
|
||||
async function (statusCode, responseTime, responseRaw, modules) {
|
||||
if (statusCode !== 200) {
|
||||
return { status: "DOWN", latency: responseTime }
|
||||
}
|
||||
@@ -99,5 +99,5 @@ SECRET_PARAM=your_real_secret_value
|
||||
status: status ? "UP" : "DOWN",
|
||||
latency: responseTime
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1,582 +0,0 @@
|
||||
<script lang="ts">
|
||||
import * as Card from "$lib/components/ui/card/index.js";
|
||||
import * as Dialog from "$lib/components/ui/dialog/index.js";
|
||||
import * as Select from "$lib/components/ui/select/index.js";
|
||||
import * as AlertDialog from "$lib/components/ui/alert-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 { Checkbox } from "$lib/components/ui/checkbox/index.js";
|
||||
import { Switch } from "$lib/components/ui/switch/index.js";
|
||||
import { Textarea } from "$lib/components/ui/textarea/index.js";
|
||||
import { Spinner } from "$lib/components/ui/spinner/index.js";
|
||||
import { Badge } from "$lib/components/ui/badge/index.js";
|
||||
import * as Item from "$lib/components/ui/item/index.js";
|
||||
import PlusIcon from "@lucide/svelte/icons/plus";
|
||||
import EditIcon from "@lucide/svelte/icons/pencil";
|
||||
import TrashIcon from "@lucide/svelte/icons/trash";
|
||||
import BellIcon from "@lucide/svelte/icons/bell";
|
||||
import BellOffIcon from "@lucide/svelte/icons/bell-off";
|
||||
import GC from "$lib/global-constants";
|
||||
import { onMount } from "svelte";
|
||||
import type { TriggerRecord } from "$lib/server/types/db";
|
||||
import clientResolver from "$lib/client/resolver.js";
|
||||
import type {
|
||||
MonitorAlertConfigWithTriggers,
|
||||
AlertForType,
|
||||
AlertSeverityType,
|
||||
YesNoType
|
||||
} from "$lib/server/types/db";
|
||||
import { getAlertText } from "$lib/alerts/alert-text";
|
||||
import { resolve } from "$app/paths";
|
||||
import { toast } from "svelte-sonner";
|
||||
|
||||
let { monitor_tag }: { monitor_tag: string } = $props();
|
||||
|
||||
// State
|
||||
let triggers: TriggerRecord[] = $state([]);
|
||||
let alertConfigs: MonitorAlertConfigWithTriggers[] = $state([]);
|
||||
let loading = $state(false);
|
||||
let saving = $state(false);
|
||||
let dialogOpen = $state(false);
|
||||
let deleteDialogOpen = $state(false);
|
||||
let alertToDelete: MonitorAlertConfigWithTriggers | null = $state(null);
|
||||
|
||||
const base = resolve("/");
|
||||
|
||||
// Form state
|
||||
let isEditing = $state(false);
|
||||
let editingId: number | null = $state(null);
|
||||
|
||||
// Default form values
|
||||
const defaultForm = {
|
||||
alert_for: "STATUS" as AlertForType,
|
||||
alert_value: "DOWN",
|
||||
failure_threshold: 3,
|
||||
success_threshold: 1,
|
||||
alert_description: "",
|
||||
create_incident: "NO" as YesNoType,
|
||||
is_active: "YES" as YesNoType,
|
||||
severity: "WARNING" as AlertSeverityType,
|
||||
trigger_ids: [] as number[]
|
||||
};
|
||||
|
||||
let form = $state({ ...defaultForm });
|
||||
|
||||
// Alert type options
|
||||
const alertForOptions: { value: AlertForType; label: string }[] = [
|
||||
{ value: GC.STATUS, label: GC.STATUS },
|
||||
{ value: GC.LATENCY, label: GC.LATENCY },
|
||||
{ value: GC.UPTIME, label: GC.UPTIME }
|
||||
];
|
||||
|
||||
const statusValueOptions = [
|
||||
{ value: GC.DOWN, label: GC.DOWN },
|
||||
{ value: GC.DEGRADED, label: GC.DEGRADED }
|
||||
];
|
||||
|
||||
const severityOptions: { value: AlertSeverityType; label: string }[] = [
|
||||
{ value: GC.CRITICAL, label: GC.CRITICAL },
|
||||
{ value: GC.WARNING, label: GC.WARNING }
|
||||
];
|
||||
|
||||
const yesNoOptions: { value: YesNoType; label: string }[] = [
|
||||
{ value: GC.YES, label: GC.YES },
|
||||
{ value: GC.NO, label: GC.NO }
|
||||
];
|
||||
|
||||
// Computed - value label based on alert type
|
||||
const alertValueLabel = $derived(getAlertText({ kind: "label", alert_for: form.alert_for }));
|
||||
const alertValueHelp = $derived(getAlertText({ kind: "help", alert_for: form.alert_for }));
|
||||
|
||||
// Reset alert value when alert_for changes
|
||||
function handleAlertForChange(newValue: AlertForType) {
|
||||
form.alert_for = newValue;
|
||||
if (newValue === GC.STATUS) {
|
||||
form.alert_value = GC.DOWN;
|
||||
} else if (newValue === GC.LATENCY) {
|
||||
form.alert_value = "1000";
|
||||
} else if (newValue === GC.UPTIME) {
|
||||
form.alert_value = "99";
|
||||
}
|
||||
}
|
||||
|
||||
// API calls
|
||||
async function loadTriggersData() {
|
||||
try {
|
||||
const response = await fetch(clientResolver(resolve, "manage/api"), {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
action: "getTriggers",
|
||||
data: { status: GC.ACTIVE }
|
||||
})
|
||||
});
|
||||
triggers = await response.json();
|
||||
} catch (error) {
|
||||
console.error("Failed to load triggers", error);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadAlertConfigs() {
|
||||
try {
|
||||
const response = await fetch(clientResolver(resolve, "manage/api"), {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
action: "getMonitorAlertConfigsByMonitorTag",
|
||||
data: { monitor_tag }
|
||||
})
|
||||
});
|
||||
const result = await response.json();
|
||||
if (result.error) {
|
||||
toast.error(result.error);
|
||||
} else {
|
||||
alertConfigs = result;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load alert configs", error);
|
||||
}
|
||||
}
|
||||
|
||||
async function saveAlertConfig() {
|
||||
saving = true;
|
||||
try {
|
||||
const action = isEditing ? "updateMonitorAlertConfig" : "createMonitorAlertConfig";
|
||||
const data: Record<string, unknown> = {
|
||||
monitor_tag,
|
||||
alert_for: form.alert_for,
|
||||
alert_value: form.alert_value,
|
||||
failure_threshold: form.failure_threshold,
|
||||
success_threshold: form.success_threshold,
|
||||
alert_description: form.alert_description || null,
|
||||
create_incident: form.create_incident,
|
||||
severity: form.severity,
|
||||
trigger_ids: form.trigger_ids
|
||||
};
|
||||
|
||||
if (isEditing && editingId) {
|
||||
data.id = editingId;
|
||||
data.is_active = form.is_active;
|
||||
}
|
||||
|
||||
const response = await fetch(clientResolver(resolve, "manage/api"), {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ action, data })
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
if (result.error) {
|
||||
toast.error(result.error);
|
||||
} else {
|
||||
toast.success(isEditing ? "Alert updated successfully" : "Alert created successfully");
|
||||
dialogOpen = false;
|
||||
resetForm();
|
||||
await loadAlertConfigs();
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error("Failed to save alert");
|
||||
} finally {
|
||||
saving = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteAlertConfig() {
|
||||
if (!alertToDelete) return;
|
||||
|
||||
try {
|
||||
const response = await fetch(clientResolver(resolve, "manage/api"), {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
action: "deleteMonitorAlertConfig",
|
||||
data: { id: alertToDelete.id }
|
||||
})
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
if (result.error) {
|
||||
toast.error(result.error);
|
||||
} else {
|
||||
toast.success("Alert deleted successfully");
|
||||
await loadAlertConfigs();
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error("Failed to delete alert");
|
||||
} finally {
|
||||
deleteDialogOpen = false;
|
||||
alertToDelete = null;
|
||||
}
|
||||
}
|
||||
|
||||
async function toggleAlertStatus(alert: MonitorAlertConfigWithTriggers) {
|
||||
try {
|
||||
const response = await fetch(clientResolver(resolve, "manage/api"), {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
action: "toggleMonitorAlertConfigStatus",
|
||||
data: { id: alert.id }
|
||||
})
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
if (result.error) {
|
||||
toast.error(result.error);
|
||||
} else {
|
||||
toast.success(result.is_active === "YES" ? "Alert activated" : "Alert deactivated");
|
||||
await loadAlertConfigs();
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error("Failed to toggle alert status");
|
||||
}
|
||||
}
|
||||
|
||||
// Form handlers
|
||||
function resetForm() {
|
||||
form = { ...defaultForm };
|
||||
isEditing = false;
|
||||
editingId = null;
|
||||
}
|
||||
|
||||
function openCreateDialog() {
|
||||
resetForm();
|
||||
dialogOpen = true;
|
||||
}
|
||||
|
||||
function openEditDialog(alert: MonitorAlertConfigWithTriggers) {
|
||||
isEditing = true;
|
||||
editingId = alert.id;
|
||||
form = {
|
||||
alert_for: alert.alert_for,
|
||||
alert_value: alert.alert_value,
|
||||
failure_threshold: alert.failure_threshold,
|
||||
success_threshold: alert.success_threshold,
|
||||
alert_description: alert.alert_description || "",
|
||||
create_incident: alert.create_incident,
|
||||
is_active: alert.is_active,
|
||||
severity: alert.severity,
|
||||
trigger_ids: alert.triggers.map((t) => t.id)
|
||||
};
|
||||
dialogOpen = true;
|
||||
}
|
||||
|
||||
function openDeleteDialog(alert: MonitorAlertConfigWithTriggers) {
|
||||
alertToDelete = alert;
|
||||
deleteDialogOpen = true;
|
||||
}
|
||||
|
||||
function toggleTrigger(triggerId: number) {
|
||||
if (form.trigger_ids.includes(triggerId)) {
|
||||
form.trigger_ids = form.trigger_ids.filter((id) => id !== triggerId);
|
||||
} else {
|
||||
form.trigger_ids = [...form.trigger_ids, triggerId];
|
||||
}
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
loading = true;
|
||||
await Promise.all([loadTriggersData(), loadAlertConfigs()]);
|
||||
loading = false;
|
||||
});
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<Card.Root>
|
||||
<Card.Header class="relative">
|
||||
<Card.Title>Alerting</Card.Title>
|
||||
<Card.Description
|
||||
>Alert configurations define when and how you get notified about monitor issues.</Card.Description
|
||||
>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
class="absolute top-4 right-4 cursor-pointer"
|
||||
disabled={loading}
|
||||
size="sm"
|
||||
onclick={openCreateDialog}
|
||||
>
|
||||
<PlusIcon class="size-4" /> Alert
|
||||
</Button>
|
||||
</Card.Header>
|
||||
<Card.Content>
|
||||
{#if loading}
|
||||
<div class="flex w-full flex-col items-center gap-4 py-8">
|
||||
<Spinner class="size-6" />
|
||||
<p class="text-muted-foreground text-sm">Loading alerts...</p>
|
||||
</div>
|
||||
{:else if alertConfigs.length === 0}
|
||||
<div class="flex flex-col items-center gap-2 py-8 text-center">
|
||||
<BellOffIcon class="text-muted-foreground size-12" />
|
||||
<p class="text-muted-foreground text-sm">No alerts configured yet</p>
|
||||
<p class="text-muted-foreground text-xs">Create an alert to get notified when your monitor has issues</p>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex flex-col gap-3">
|
||||
{#each alertConfigs as alert (alert.id)}
|
||||
<div
|
||||
class="bg-muted/50 flex items-start justify-between gap-4 rounded-lg border p-4 {alert.is_active === GC.NO
|
||||
? 'opacity-60'
|
||||
: ''}"
|
||||
>
|
||||
<div class="flex-1 space-y-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<Badge variant={alert.severity === "CRITICAL" ? "destructive" : "secondary"}>
|
||||
{alert.severity}
|
||||
</Badge>
|
||||
<Badge variant="outline">{alert.alert_for}</Badge>
|
||||
{#if alert.is_active === GC.NO}
|
||||
<Badge variant="outline" class="text-muted-foreground">Inactive</Badge>
|
||||
{/if}
|
||||
{#if alert.create_incident === GC.YES}
|
||||
<Badge variant="outline">Creates Incident</Badge>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<p class="text-sm">
|
||||
{getAlertText({
|
||||
kind: "description",
|
||||
alert_for: alert.alert_for,
|
||||
alert_value: alert.alert_value,
|
||||
failure_threshold: alert.failure_threshold,
|
||||
success_threshold: alert.success_threshold
|
||||
})}
|
||||
</p>
|
||||
|
||||
{#if alert.alert_description}
|
||||
<p class="text-muted-foreground text-xs">{alert.alert_description}</p>
|
||||
{/if}
|
||||
|
||||
{#if alert.triggers.length > 0}
|
||||
<div class="flex flex-wrap gap-1">
|
||||
{#each alert.triggers as trigger (trigger.id)}
|
||||
<Badge variant="outline" class="text-xs">
|
||||
{trigger.name}
|
||||
</Badge>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<Switch checked={alert.is_active === GC.YES} onCheckedChange={() => toggleAlertStatus(alert)} />
|
||||
<Button variant="ghost" size="icon" onclick={() => openEditDialog(alert)}>
|
||||
<EditIcon class="size-4" />
|
||||
</Button>
|
||||
<Button variant="ghost" size="icon" onclick={() => openDeleteDialog(alert)}>
|
||||
<TrashIcon class="size-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
|
||||
<!-- Create/Edit Dialog -->
|
||||
<Dialog.Root bind:open={dialogOpen} onOpenChange={(o) => !o && resetForm()}>
|
||||
<Dialog.Content class="max-h-[90vh] overflow-y-auto sm:max-w-lg">
|
||||
<Dialog.Header>
|
||||
<Dialog.Title>{isEditing ? "Edit Alert" : "Create New Alert"}</Dialog.Title>
|
||||
<Dialog.Description>
|
||||
Configure when to trigger an alert and which notification channels to use.
|
||||
</Dialog.Description>
|
||||
</Dialog.Header>
|
||||
|
||||
<div class="flex flex-col gap-4 py-4">
|
||||
<!-- Alert For -->
|
||||
<div class="flex flex-col gap-2">
|
||||
<Label for="alert-for">Alert Type</Label>
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={form.alert_for}
|
||||
onValueChange={(v) => v && handleAlertForChange(v as AlertForType)}
|
||||
>
|
||||
<Select.Trigger id="alert-for" class="w-full">
|
||||
{alertForOptions.find((o) => o.value === form.alert_for)?.label || "Select type"}
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
{#each alertForOptions as option}
|
||||
<Select.Item value={option.value}>{option.label}</Select.Item>
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
|
||||
<!-- Alert Value -->
|
||||
<div class="flex flex-col gap-2">
|
||||
<Label for="alert-value">{alertValueLabel}</Label>
|
||||
{#if form.alert_for === "STATUS"}
|
||||
<Select.Root type="single" value={form.alert_value} onValueChange={(v) => v && (form.alert_value = v)}>
|
||||
<Select.Trigger id="alert-value" class="w-full">
|
||||
{form.alert_value}
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
{#each statusValueOptions as option}
|
||||
<Select.Item value={option.value}>{option.label}</Select.Item>
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
{:else}
|
||||
<Input
|
||||
id="alert-value"
|
||||
type="number"
|
||||
min={form.alert_for === GC.UPTIME ? "0" : "1"}
|
||||
max={form.alert_for === GC.UPTIME ? "100" : undefined}
|
||||
bind:value={form.alert_value}
|
||||
/>
|
||||
{/if}
|
||||
<p class="text-muted-foreground text-xs">{alertValueHelp}</p>
|
||||
</div>
|
||||
|
||||
<!-- Thresholds -->
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="flex flex-col gap-2">
|
||||
<Label for="failure-threshold">Failure Threshold</Label>
|
||||
<Input id="failure-threshold" type="number" min="1" bind:value={form.failure_threshold} />
|
||||
<p class="text-muted-foreground text-xs">Consecutive failures before alert</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<Label for="success-threshold">Success Threshold</Label>
|
||||
<Input id="success-threshold" type="number" min="1" bind:value={form.success_threshold} />
|
||||
<p class="text-muted-foreground text-xs">Consecutive successes to resolve</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Severity -->
|
||||
<div class="flex flex-col gap-2">
|
||||
<Label for="severity">Severity</Label>
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={form.severity}
|
||||
onValueChange={(v) => v && (form.severity = v as AlertSeverityType)}
|
||||
>
|
||||
<Select.Trigger id="severity" class="w-full">
|
||||
{severityOptions.find((o) => o.value === form.severity)?.label || "Select severity"}
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
{#each severityOptions as option}
|
||||
<Select.Item value={option.value}>{option.label}</Select.Item>
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
|
||||
<!-- Create Incident -->
|
||||
<div class="flex flex-col gap-2">
|
||||
<Label for="create-incident">Create Incident</Label>
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={form.create_incident}
|
||||
onValueChange={(v) => v && (form.create_incident = v as YesNoType)}
|
||||
>
|
||||
<Select.Trigger id="create-incident" class="w-full">
|
||||
{form.create_incident}
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
{#each yesNoOptions as option}
|
||||
<Select.Item value={option.value}>{option.label}</Select.Item>
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
<p class="text-muted-foreground text-xs">Automatically create an incident when this alert triggers</p>
|
||||
</div>
|
||||
|
||||
<!-- Is Active (only when editing) -->
|
||||
{#if isEditing}
|
||||
<div class="flex flex-col gap-2">
|
||||
<Label for="is-active">Active</Label>
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={form.is_active}
|
||||
onValueChange={(v) => v && (form.is_active = v as YesNoType)}
|
||||
>
|
||||
<Select.Trigger id="is-active" class="w-full">
|
||||
{form.is_active}
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
{#each yesNoOptions as option}
|
||||
<Select.Item value={option.value}>{option.label}</Select.Item>
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Description -->
|
||||
<div class="flex flex-col gap-2">
|
||||
<Label for="alert-description">Description (optional)</Label>
|
||||
<Textarea
|
||||
id="alert-description"
|
||||
placeholder="Add a description for this alert..."
|
||||
bind:value={form.alert_description}
|
||||
rows={2}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Triggers -->
|
||||
{#if triggers.length > 0}
|
||||
<div class="flex flex-col gap-2">
|
||||
<Label>Notification Triggers (optional)</Label>
|
||||
<p class="text-muted-foreground text-xs">Select which triggers to notify when this alert fires</p>
|
||||
<div class="mt-2 grid gap-2">
|
||||
{#each triggers as trigger (trigger.id)}
|
||||
<label
|
||||
class="bg-muted/30 hover:bg-muted/50 flex cursor-pointer items-center gap-3 rounded-md border p-3"
|
||||
>
|
||||
<Checkbox
|
||||
checked={form.trigger_ids.includes(trigger.id)}
|
||||
onCheckedChange={() => toggleTrigger(trigger.id)}
|
||||
/>
|
||||
<div class="flex-1">
|
||||
<p class="text-sm font-medium">{trigger.name}</p>
|
||||
{#if trigger.trigger_desc}
|
||||
<p class="text-muted-foreground text-xs">{trigger.trigger_desc}</p>
|
||||
{/if}
|
||||
</div>
|
||||
<Badge variant="outline" class="text-xs capitalize">{trigger.trigger_type}</Badge>
|
||||
</label>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<p class="text-muted-foreground text-sm">
|
||||
No notification triggers available. <a
|
||||
href={clientResolver(resolve, "/manage/app/triggers")}
|
||||
class="underline">Create a trigger</a
|
||||
> to receive notifications.
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<Dialog.Footer>
|
||||
<Button variant="outline" onclick={() => (dialogOpen = false)} disabled={saving}>Cancel</Button>
|
||||
<Button onclick={saveAlertConfig} disabled={saving}>
|
||||
{#if saving}
|
||||
<Spinner class="size-4" />
|
||||
{/if}
|
||||
{isEditing ? "Update Alert" : "Create Alert"}
|
||||
</Button>
|
||||
</Dialog.Footer>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
|
||||
<!-- Delete Confirmation Dialog -->
|
||||
<AlertDialog.Root bind:open={deleteDialogOpen}>
|
||||
<AlertDialog.Content>
|
||||
<AlertDialog.Header>
|
||||
<AlertDialog.Title>Delete Alert</AlertDialog.Title>
|
||||
<AlertDialog.Description>
|
||||
Are you sure you want to delete this alert? This action cannot be undone.
|
||||
</AlertDialog.Description>
|
||||
</AlertDialog.Header>
|
||||
<AlertDialog.Footer>
|
||||
<AlertDialog.Cancel>Cancel</AlertDialog.Cancel>
|
||||
<AlertDialog.Action onclick={deleteAlertConfig}>Delete</AlertDialog.Action>
|
||||
</AlertDialog.Footer>
|
||||
</AlertDialog.Content>
|
||||
</AlertDialog.Root>
|
||||
</div>
|
||||
@@ -1,11 +0,0 @@
|
||||
import { redirect } from "@sveltejs/kit";
|
||||
import type { RequestHandler } from "./$types";
|
||||
import { getDocsRootConfig } from "../(docs)/docs/docs-utils.server";
|
||||
|
||||
export const GET: RequestHandler = () => {
|
||||
const rootConfig = getDocsRootConfig();
|
||||
const latestVersion = rootConfig.versions.find((v) => v.latest) ?? rootConfig.versions[0];
|
||||
const versionSlug = latestVersion?.slug ?? "v4";
|
||||
|
||||
throw redirect(301, `/docs/${versionSlug}/llms.txt`);
|
||||
};
|
||||
@@ -1,53 +0,0 @@
|
||||
import type { RequestHandler } from "@sveltejs/kit";
|
||||
import { getDocsRootConfig, getVersionDocsUrls } from "../(docs)/docs/docs-utils.server";
|
||||
|
||||
const BASE_DOMAIN = "https://kener.ing";
|
||||
|
||||
export const GET: RequestHandler = () => {
|
||||
const rootConfig = getDocsRootConfig();
|
||||
const latestVersion = rootConfig.versions.find((v) => v.latest) ?? rootConfig.versions[0];
|
||||
|
||||
const urls: { loc: string; priority: string; changefreq: string }[] = [
|
||||
{ loc: `${BASE_DOMAIN}/docs`, priority: "1.0", changefreq: "weekly" },
|
||||
];
|
||||
|
||||
if (latestVersion) {
|
||||
const docsUrls = getVersionDocsUrls(latestVersion.slug, BASE_DOMAIN);
|
||||
for (const url of docsUrls) {
|
||||
// Skip raw markdown URLs and external URLs
|
||||
if (url.includes("/docs/raw/") || !url.startsWith(BASE_DOMAIN)) continue;
|
||||
urls.push({ loc: url, priority: "0.8", changefreq: "weekly" });
|
||||
}
|
||||
|
||||
// Add llms.txt for AI discoverability
|
||||
urls.push({
|
||||
loc: `${BASE_DOMAIN}/docs/${latestVersion.slug}/llms.txt`,
|
||||
priority: "0.5",
|
||||
changefreq: "weekly",
|
||||
});
|
||||
}
|
||||
|
||||
const xml = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
${urls
|
||||
.map(
|
||||
(u) => ` <url>
|
||||
<loc>${escapeXml(u.loc)}</loc>
|
||||
<changefreq>${u.changefreq}</changefreq>
|
||||
<priority>${u.priority}</priority>
|
||||
</url>`,
|
||||
)
|
||||
.join("\n")}
|
||||
</urlset>`;
|
||||
|
||||
return new Response(xml, {
|
||||
headers: {
|
||||
"content-type": "application/xml; charset=utf-8",
|
||||
"cache-control": "public, max-age=3600",
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
function escapeXml(str: string): string {
|
||||
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
||||
}
|
||||
Reference in New Issue
Block a user