diff --git a/web/components.json b/web/components.json new file mode 100644 index 0000000..d5f55d3 --- /dev/null +++ b/web/components.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://www.shadcn-svelte.com/schema.json", + "tailwind": { + "css": "src/routes/layout.css", + "baseColor": "neutral" + }, + "aliases": { + "components": "$lib/components", + "utils": "$lib/utils", + "ui": "$lib/components/ui", + "hooks": "$lib/hooks", + "lib": "$lib" + }, + "typescript": true, + "iconLibrary": "lucide" +} diff --git a/web/package-lock.json b/web/package-lock.json index 9dc33c8..5df0e35 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -8,8 +8,6 @@ "name": "web", "version": "0.0.1", "dependencies": { - "@internationalized/date": "^3.8.1", - "@lucide/svelte": "^1.7.0", "bits-ui": "^2.17.2", "clsx": "^2.1.1", "svelte-sonner": "^1.1.0", @@ -18,6 +16,8 @@ "tw-animate-css": "^1.4.0" }, "devDependencies": { + "@internationalized/date": "^3.12.0", + "@lucide/svelte": "^1.7.0", "@sveltejs/adapter-static": "^3.0.10", "@sveltejs/kit": "^2.50.2", "@sveltejs/vite-plugin-svelte": "^6.2.4", @@ -531,6 +531,7 @@ "version": "1.7.0", "resolved": "https://registry.npmjs.org/@lucide/svelte/-/svelte-1.7.0.tgz", "integrity": "sha512-YytBKOUBGox7yWcykZnYxOkn5WpR5G1qYXLYXV/j1B79SOTTEKzB+s5yF5Rq9l9OkweDStNH2b4yTqfvhEhV8g==", + "dev": true, "license": "ISC", "peerDependencies": { "svelte": "^5" diff --git a/web/package.json b/web/package.json index 5be9334..54a1a67 100644 --- a/web/package.json +++ b/web/package.json @@ -14,6 +14,8 @@ "format": "prettier --write ." }, "devDependencies": { + "@internationalized/date": "^3.12.0", + "@lucide/svelte": "^1.7.0", "@sveltejs/adapter-static": "^3.0.10", "@sveltejs/kit": "^2.50.2", "@sveltejs/vite-plugin-svelte": "^6.2.4", @@ -28,8 +30,6 @@ "vite": "^7.3.1" }, "dependencies": { - "@internationalized/date": "^3.8.1", - "@lucide/svelte": "^1.7.0", "bits-ui": "^2.17.2", "clsx": "^2.1.1", "svelte-sonner": "^1.1.0", diff --git a/web/src/lib/AppShell.svelte b/web/src/lib/AppShell.svelte index 49f0c39..9115c8f 100644 --- a/web/src/lib/AppShell.svelte +++ b/web/src/lib/AppShell.svelte @@ -1,60 +1,158 @@ +
+
{@render children?.()}
+
diff --git a/web/src/lib/api/client.ts b/web/src/lib/api/client.ts index f53f4b6..961192d 100644 --- a/web/src/lib/api/client.ts +++ b/web/src/lib/api/client.ts @@ -3,44 +3,84 @@ import { browser } from '$app/environment'; export const TOKEN_STORAGE_KEY = 'evobgp_api_token'; export type Problem = { + type?: string; title?: string; status?: number; detail?: string; }; -function mergeHeaders(init?: RequestInit): Headers { +function getToken(): string | null { + if (!browser) return null; + return localStorage.getItem(TOKEN_STORAGE_KEY); +} + +function mergeHeaders(init?: RequestInit, extraHeaders?: Record): Headers { const h = new Headers(init?.headers); - if (!h.has('Accept')) { - h.set('Accept', 'application/json'); - } - if (browser) { - const t = localStorage.getItem(TOKEN_STORAGE_KEY); - if (t && !h.has('Authorization')) { - h.set('Authorization', `Bearer ${t}`); + if (!h.has('Accept')) h.set('Accept', 'application/json'); + const t = getToken(); + if (t && !h.has('Authorization')) h.set('Authorization', `Bearer ${t}`); + if (extraHeaders) { + for (const [k, v] of Object.entries(extraHeaders)) { + if (!h.has(k)) h.set(k, v); } } return h; } -export async function apiFetch(path: string, init?: RequestInit): Promise { - if (!browser) { - throw new Error('API is only available in the browser'); +export class ApiError extends Error { + constructor( + public readonly status: number, + message: string, + public readonly problem?: Problem + ) { + super(message); } +} + +export async function apiFetch(path: string, init?: RequestInit): Promise { + if (!browser) throw new Error('API is only available in the browser'); return fetch(path, { ...init, headers: mergeHeaders(init) }); } +/** GET / DELETE без тела */ export async function apiJSON(path: string, init?: RequestInit): Promise { const res = await apiFetch(path, init); + return parseResponse(res); +} + +/** POST / PATCH / PUT с JSON-телом и автоматическим Idempotency-Key */ +export async function apiMutate( + path: string, + method: 'POST' | 'PATCH' | 'PUT' | 'DELETE', + body?: unknown, + opts?: { idempotent?: boolean } +): Promise { + const headers: Record = {}; + if (body !== undefined) headers['Content-Type'] = 'application/json'; + if (opts?.idempotent !== false) { + headers['Idempotency-Key'] = crypto.randomUUID(); + } + const res = await fetch(path, { + method, + headers: mergeHeaders({ headers }, headers), + body: body !== undefined ? JSON.stringify(body) : undefined + }); + return parseResponse(res); +} + +async function parseResponse(res: Response): Promise { + if (res.status === 204 || res.status === 205) return undefined as T; const text = await res.text(); if (!res.ok) { - let detail = text; + let problem: Problem | undefined; + let detail = `HTTP ${res.status}`; try { - const j = JSON.parse(text) as Problem; - if (j?.detail) detail = j.detail; + problem = JSON.parse(text) as Problem; + detail = problem.detail ?? problem.title ?? detail; } catch { - /* plain text */ + if (text) detail = text; } - throw new Error(detail || `HTTP ${res.status}`); + throw new ApiError(res.status, detail, problem); } if (!text) return undefined as T; return JSON.parse(text) as T; diff --git a/web/src/lib/api/types.ts b/web/src/lib/api/types.ts index 555f829..eb91cf8 100644 --- a/web/src/lib/api/types.ts +++ b/web/src/lib/api/types.ts @@ -1,21 +1,152 @@ -export type ModuleRow = { - id: string; - type: string; - name: string; - enabled: boolean; - priority: number; - refresh_interval_sec: number; - cron_expr: string; - default_community_id: string | null; - doh_profile_id: string | null; -}; - -export type ModulesResponse = { - items: ModuleRow[]; +// ---- Pagination ---- +export type Page = { + items: T[]; next_cursor: string | null; has_more: boolean; }; +// ---- Modules ---- +export type ModuleType = 'AS_PREFIXES' | 'CDN_CIDRS' | 'DOMAINS' | 'IP_RANGES'; + +export type ModuleRow = { + id: string; + type: ModuleType; + name: string; + enabled: boolean; + priority: number; + refresh_interval_sec: number | null; + cron_expr: string | null; + default_community_id: string | null; + doh_profile_id: string | null; +}; +export type ModulesResponse = Page; + +export type ModuleCreate = { + type: ModuleType; + name: string; + enabled?: boolean; + priority?: number; + doh_profile_id?: string | null; + refresh_interval_sec?: number | null; + cron_expr?: string | null; + default_community_id?: string | null; +}; +export type ModulePatch = Partial>; + +// ---- AS Entries ---- +export type AsEntry = { + id: string; + asn: number | null; + prefix: string | null; + community_id: string | null; +}; +export type AsEntryCreate = { + asn?: number; + prefix?: string; + community_id?: string | null; +}; +export type AsEntriesResponse = Page; + +// ---- CDN Sources ---- +export type CdnSource = { + id: string; + url: string; + source_kind: string; + community_id: string | null; + refresh_interval_sec: number | null; +}; +export type CdnSourceCreate = { + url: string; + source_kind: string; + community_id?: string | null; +}; +export type CdnSourcePatch = Partial & { refresh_interval_sec?: number | null }; +export type CdnSourcesResponse = Page; + +// ---- Domain Entries ---- +export type DomainEntry = { + id: string; + fqdn: string; + community_id: string | null; +}; +export type DomainEntryCreate = { + fqdn: string; + community_id?: string | null; +}; +export type DomainEntriesResponse = Page; + +// ---- IP Range Entries ---- +export type IpRangeEntry = { + id: string; + prefix: string; + community_id: string; +}; +export type IpRangeEntryCreate = { + prefix: string; + community_id: string; +}; +export type IpRangeEntriesResponse = Page; + +// ---- DoH Profiles ---- +export type DohProfile = { + id: string; + url: string; + timeout_ms: number | null; + vault_secret_ref: string | null; +}; +export type DohProfileCreate = { + url: string; + timeout_ms?: number | null; + vault_secret_ref?: string | null; +}; +export type DohProfilePatch = Partial; +export type DohProfilesResponse = Page; + +// ---- Communities ---- +export type BgpCommunity = { + id: string; + name: string; + kind: string | null; +}; +export type BgpCommunityCreate = { + name: string; + kind?: string; +}; +export type BgpCommunityPatch = Partial; +export type CommunitiesResponse = Page; + +// ---- Peers ---- +export type PeerRow = { + id: string; + name?: string; + neighbor: string; + remote_asn?: number; + session_state: string; + bgp_speaker_id: string | null; +}; +export type PeersResponse = Page; +export type BgpPeerCreate = { + neighbor: string; + remote_asn: number; + bgp_speaker_id?: string | null; +}; +export type BgpPeerPatch = Partial; + +// ---- Speakers ---- +export type SpeakerRow = { + id: string; + role: string; + endpoint: string; + last_applied_revision_id: string | null; +}; +export type SpeakersResponse = Page; +export type BgpSpeakerCreate = { + endpoint: string; + role?: string; +}; +export type BgpSpeakerPatch = Partial; + +// ---- Revisions ---- export type RevisionRow = { id: string; content_hash: string; @@ -24,40 +155,27 @@ export type RevisionRow = { materialized_prefix_count: number; module_id: string | null; }; +export type RevisionsResponse = Page; -export type RevisionsResponse = { - items: RevisionRow[]; - next_cursor: string | null; - has_more: boolean; +export type RevisionPrefix = { + prefix: string; + community_id?: string | null; }; +export type RevisionPrefixesResponse = Page; -export type PeerRow = { +export type RevisionPreview = { id: string; - name: string; - neighbor: string; - session_state: string; - bgp_speaker_id: string | null; + prefixes?: RevisionPrefix[]; + [key: string]: unknown; }; -export type PeersResponse = { - items: PeerRow[]; - next_cursor: string | null; - has_more: boolean; -}; - -export type SpeakerRow = { - id: string; - role: string; - endpoint: string; - last_applied_revision_id: string | null; -}; - -export type SpeakersResponse = { - items: SpeakerRow[]; - next_cursor: string | null; - has_more: boolean; +export type RevisionDiff = { + added: RevisionPrefix[]; + removed: RevisionPrefix[]; + [key: string]: unknown; }; +// ---- Jobs ---- export type JobRow = { job_id: string; kind: string; @@ -69,9 +187,7 @@ export type JobRow = { error?: string | null; meta?: Record; }; +export type JobsResponse = Page; -export type JobsResponse = { - items: JobRow[]; - next_cursor: string | null; - has_more: boolean; -}; +// ---- Settings ---- +export type AppSettings = Record; diff --git a/web/src/lib/components/ui/alert-dialog/alert-dialog-action.svelte b/web/src/lib/components/ui/alert-dialog/alert-dialog-action.svelte new file mode 100644 index 0000000..7e63004 --- /dev/null +++ b/web/src/lib/components/ui/alert-dialog/alert-dialog-action.svelte @@ -0,0 +1,27 @@ + + + diff --git a/web/src/lib/components/ui/alert-dialog/alert-dialog-cancel.svelte b/web/src/lib/components/ui/alert-dialog/alert-dialog-cancel.svelte new file mode 100644 index 0000000..a4ce03c --- /dev/null +++ b/web/src/lib/components/ui/alert-dialog/alert-dialog-cancel.svelte @@ -0,0 +1,27 @@ + + + diff --git a/web/src/lib/components/ui/alert-dialog/alert-dialog-content.svelte b/web/src/lib/components/ui/alert-dialog/alert-dialog-content.svelte new file mode 100644 index 0000000..c9b3eb3 --- /dev/null +++ b/web/src/lib/components/ui/alert-dialog/alert-dialog-content.svelte @@ -0,0 +1,32 @@ + + + + + + diff --git a/web/src/lib/components/ui/alert-dialog/alert-dialog-description.svelte b/web/src/lib/components/ui/alert-dialog/alert-dialog-description.svelte new file mode 100644 index 0000000..5024f3f --- /dev/null +++ b/web/src/lib/components/ui/alert-dialog/alert-dialog-description.svelte @@ -0,0 +1,17 @@ + + + diff --git a/web/src/lib/components/ui/alert-dialog/alert-dialog-footer.svelte b/web/src/lib/components/ui/alert-dialog/alert-dialog-footer.svelte new file mode 100644 index 0000000..f4214db --- /dev/null +++ b/web/src/lib/components/ui/alert-dialog/alert-dialog-footer.svelte @@ -0,0 +1,23 @@ + + +
+ {@render children?.()} +
diff --git a/web/src/lib/components/ui/alert-dialog/alert-dialog-header.svelte b/web/src/lib/components/ui/alert-dialog/alert-dialog-header.svelte new file mode 100644 index 0000000..d6df4d3 --- /dev/null +++ b/web/src/lib/components/ui/alert-dialog/alert-dialog-header.svelte @@ -0,0 +1,20 @@ + + +
+ {@render children?.()} +
diff --git a/web/src/lib/components/ui/alert-dialog/alert-dialog-media.svelte b/web/src/lib/components/ui/alert-dialog/alert-dialog-media.svelte new file mode 100644 index 0000000..3f634f3 --- /dev/null +++ b/web/src/lib/components/ui/alert-dialog/alert-dialog-media.svelte @@ -0,0 +1,20 @@ + + +
+ {@render children?.()} +
diff --git a/web/src/lib/components/ui/alert-dialog/alert-dialog-overlay.svelte b/web/src/lib/components/ui/alert-dialog/alert-dialog-overlay.svelte new file mode 100644 index 0000000..9ffcc85 --- /dev/null +++ b/web/src/lib/components/ui/alert-dialog/alert-dialog-overlay.svelte @@ -0,0 +1,17 @@ + + + diff --git a/web/src/lib/components/ui/alert-dialog/alert-dialog-portal.svelte b/web/src/lib/components/ui/alert-dialog/alert-dialog-portal.svelte new file mode 100644 index 0000000..f0a19a8 --- /dev/null +++ b/web/src/lib/components/ui/alert-dialog/alert-dialog-portal.svelte @@ -0,0 +1,7 @@ + + + diff --git a/web/src/lib/components/ui/alert-dialog/alert-dialog-title.svelte b/web/src/lib/components/ui/alert-dialog/alert-dialog-title.svelte new file mode 100644 index 0000000..652da33 --- /dev/null +++ b/web/src/lib/components/ui/alert-dialog/alert-dialog-title.svelte @@ -0,0 +1,17 @@ + + + diff --git a/web/src/lib/components/ui/alert-dialog/alert-dialog-trigger.svelte b/web/src/lib/components/ui/alert-dialog/alert-dialog-trigger.svelte new file mode 100644 index 0000000..b22d1d5 --- /dev/null +++ b/web/src/lib/components/ui/alert-dialog/alert-dialog-trigger.svelte @@ -0,0 +1,7 @@ + + + diff --git a/web/src/lib/components/ui/alert-dialog/alert-dialog.svelte b/web/src/lib/components/ui/alert-dialog/alert-dialog.svelte new file mode 100644 index 0000000..7ea78bb --- /dev/null +++ b/web/src/lib/components/ui/alert-dialog/alert-dialog.svelte @@ -0,0 +1,7 @@ + + + diff --git a/web/src/lib/components/ui/alert-dialog/index.ts b/web/src/lib/components/ui/alert-dialog/index.ts new file mode 100644 index 0000000..ca81c2a --- /dev/null +++ b/web/src/lib/components/ui/alert-dialog/index.ts @@ -0,0 +1,40 @@ +import Root from "./alert-dialog.svelte"; +import Portal from "./alert-dialog-portal.svelte"; +import Trigger from "./alert-dialog-trigger.svelte"; +import Title from "./alert-dialog-title.svelte"; +import Action from "./alert-dialog-action.svelte"; +import Cancel from "./alert-dialog-cancel.svelte"; +import Footer from "./alert-dialog-footer.svelte"; +import Header from "./alert-dialog-header.svelte"; +import Overlay from "./alert-dialog-overlay.svelte"; +import Content from "./alert-dialog-content.svelte"; +import Description from "./alert-dialog-description.svelte"; +import Media from "./alert-dialog-media.svelte"; + +export { + Root, + Title, + Action, + Cancel, + Portal, + Footer, + Header, + Trigger, + Overlay, + Content, + Description, + Media, + // + Root as AlertDialog, + Title as AlertDialogTitle, + Action as AlertDialogAction, + Cancel as AlertDialogCancel, + Portal as AlertDialogPortal, + Footer as AlertDialogFooter, + Header as AlertDialogHeader, + Trigger as AlertDialogTrigger, + Overlay as AlertDialogOverlay, + Content as AlertDialogContent, + Description as AlertDialogDescription, + Media as AlertDialogMedia, +}; diff --git a/web/src/lib/components/ui/alert/alert-action.svelte b/web/src/lib/components/ui/alert/alert-action.svelte new file mode 100644 index 0000000..1877d38 --- /dev/null +++ b/web/src/lib/components/ui/alert/alert-action.svelte @@ -0,0 +1,20 @@ + + +
+ {@render children?.()} +
diff --git a/web/src/lib/components/ui/alert/alert-description.svelte b/web/src/lib/components/ui/alert/alert-description.svelte new file mode 100644 index 0000000..7ee6039 --- /dev/null +++ b/web/src/lib/components/ui/alert/alert-description.svelte @@ -0,0 +1,23 @@ + + +
+ {@render children?.()} +
diff --git a/web/src/lib/components/ui/alert/alert-title.svelte b/web/src/lib/components/ui/alert/alert-title.svelte new file mode 100644 index 0000000..3e339a3 --- /dev/null +++ b/web/src/lib/components/ui/alert/alert-title.svelte @@ -0,0 +1,23 @@ + + +
svg]/alert:col-start-2 [&_a]:hover:text-foreground [&_a]:underline [&_a]:underline-offset-3", + className + )} + {...restProps} +> + {@render children?.()} +
diff --git a/web/src/lib/components/ui/alert/alert.svelte b/web/src/lib/components/ui/alert/alert.svelte new file mode 100644 index 0000000..abf7487 --- /dev/null +++ b/web/src/lib/components/ui/alert/alert.svelte @@ -0,0 +1,43 @@ + + + + + diff --git a/web/src/lib/components/ui/alert/index.ts b/web/src/lib/components/ui/alert/index.ts new file mode 100644 index 0000000..071b113 --- /dev/null +++ b/web/src/lib/components/ui/alert/index.ts @@ -0,0 +1,17 @@ +import Root from "./alert.svelte"; +import Description from "./alert-description.svelte"; +import Title from "./alert-title.svelte"; +import Action from "./alert-action.svelte"; +export { alertVariants, type AlertVariant } from "./alert.svelte"; + +export { + Root, + Description, + Title, + Action, + // + Root as Alert, + Description as AlertDescription, + Title as AlertTitle, + Action as AlertAction, +}; diff --git a/web/src/lib/components/ui/badge/badge.svelte b/web/src/lib/components/ui/badge/badge.svelte index cb378f7..51bbc23 100644 --- a/web/src/lib/components/ui/badge/badge.svelte +++ b/web/src/lib/components/ui/badge/badge.svelte @@ -1,38 +1,49 @@ -{@render children?.()} + + {@render children?.()} + diff --git a/web/src/lib/components/ui/badge/index.ts b/web/src/lib/components/ui/badge/index.ts index 5033951..64e0aa9 100644 --- a/web/src/lib/components/ui/badge/index.ts +++ b/web/src/lib/components/ui/badge/index.ts @@ -1,2 +1,2 @@ -import Root, { badgeVariants, type BadgeVariant } from './badge.svelte'; -export { Root, Root as Badge, badgeVariants, type BadgeVariant }; +export { default as Badge } from "./badge.svelte"; +export { badgeVariants, type BadgeVariant } from "./badge.svelte"; diff --git a/web/src/lib/components/ui/button/button.svelte b/web/src/lib/components/ui/button/button.svelte index 9586b4d..89cedcb 100644 --- a/web/src/lib/components/ui/button/button.svelte +++ b/web/src/lib/components/ui/button/button.svelte @@ -1,54 +1,82 @@ - +{#if href} + + {@render children?.()} + +{:else} + +{/if} diff --git a/web/src/lib/components/ui/button/index.ts b/web/src/lib/components/ui/button/index.ts index bbdc224..fb585d7 100644 --- a/web/src/lib/components/ui/button/index.ts +++ b/web/src/lib/components/ui/button/index.ts @@ -1,8 +1,17 @@ -import Root, { buttonVariants, type ButtonSize, type ButtonVariant } from './button.svelte'; +import Root, { + type ButtonProps, + type ButtonSize, + type ButtonVariant, + buttonVariants, +} from "./button.svelte"; + export { Root, + type ButtonProps as Props, + // Root as Button, buttonVariants, + type ButtonProps, type ButtonSize, - type ButtonVariant + type ButtonVariant, }; diff --git a/web/src/lib/components/ui/card/card-action.svelte b/web/src/lib/components/ui/card/card-action.svelte new file mode 100644 index 0000000..7c48844 --- /dev/null +++ b/web/src/lib/components/ui/card/card-action.svelte @@ -0,0 +1,23 @@ + + +
+ {@render children?.()} +
diff --git a/web/src/lib/components/ui/card/card-content.svelte b/web/src/lib/components/ui/card/card-content.svelte index 2dbed82..4f60ee3 100644 --- a/web/src/lib/components/ui/card/card-content.svelte +++ b/web/src/lib/components/ui/card/card-content.svelte @@ -1,12 +1,20 @@ -
{@render children?.()}
+
+ {@render children?.()} +
diff --git a/web/src/lib/components/ui/card/card-description.svelte b/web/src/lib/components/ui/card/card-description.svelte index 4527cde..9b20ac7 100644 --- a/web/src/lib/components/ui/card/card-description.svelte +++ b/web/src/lib/components/ui/card/card-description.svelte @@ -1,12 +1,20 @@ -

{@render children?.()}

+

+ {@render children?.()} +

diff --git a/web/src/lib/components/ui/card/card-footer.svelte b/web/src/lib/components/ui/card/card-footer.svelte new file mode 100644 index 0000000..1efff6d --- /dev/null +++ b/web/src/lib/components/ui/card/card-footer.svelte @@ -0,0 +1,20 @@ + + +
+ {@render children?.()} +
diff --git a/web/src/lib/components/ui/card/card-header.svelte b/web/src/lib/components/ui/card/card-header.svelte index 9094f13..7eb69e1 100644 --- a/web/src/lib/components/ui/card/card-header.svelte +++ b/web/src/lib/components/ui/card/card-header.svelte @@ -1,12 +1,23 @@ -
{@render children?.()}
+
+ {@render children?.()} +
diff --git a/web/src/lib/components/ui/card/card-title.svelte b/web/src/lib/components/ui/card/card-title.svelte index 0deb47b..1523fc5 100644 --- a/web/src/lib/components/ui/card/card-title.svelte +++ b/web/src/lib/components/ui/card/card-title.svelte @@ -1,14 +1,20 @@ -

+
{@render children?.()} -

+ diff --git a/web/src/lib/components/ui/card/card.svelte b/web/src/lib/components/ui/card/card.svelte index 0429da8..2b4f81a 100644 --- a/web/src/lib/components/ui/card/card.svelte +++ b/web/src/lib/components/ui/card/card.svelte @@ -1,17 +1,22 @@
img:first-child]:pt-0 data-[size=sm]:gap-3 data-[size=sm]:py-3 data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl group/card flex flex-col", className)} + {...restProps} > {@render children?.()}
diff --git a/web/src/lib/components/ui/card/index.ts b/web/src/lib/components/ui/card/index.ts index 318b5bb..4d3fce4 100644 --- a/web/src/lib/components/ui/card/index.ts +++ b/web/src/lib/components/ui/card/index.ts @@ -1,14 +1,25 @@ -import Root from './card.svelte'; -import Header from './card-header.svelte'; -import Title from './card-title.svelte'; -import Description from './card-description.svelte'; -import Content from './card-content.svelte'; +import Root from "./card.svelte"; +import Content from "./card-content.svelte"; +import Description from "./card-description.svelte"; +import Footer from "./card-footer.svelte"; +import Header from "./card-header.svelte"; +import Title from "./card-title.svelte"; +import Action from "./card-action.svelte"; export { Root, + Content, + Description, + Footer, + Header, + Title, + Action, + // Root as Card, + Content as CardContent, + Description as CardDescription, + Footer as CardFooter, Header as CardHeader, Title as CardTitle, - Description as CardDescription, - Content as CardContent + Action as CardAction, }; diff --git a/web/src/lib/components/ui/dialog/dialog-close.svelte b/web/src/lib/components/ui/dialog/dialog-close.svelte new file mode 100644 index 0000000..de68f2f --- /dev/null +++ b/web/src/lib/components/ui/dialog/dialog-close.svelte @@ -0,0 +1,11 @@ + + + diff --git a/web/src/lib/components/ui/dialog/dialog-content.svelte b/web/src/lib/components/ui/dialog/dialog-content.svelte new file mode 100644 index 0000000..c0551ee --- /dev/null +++ b/web/src/lib/components/ui/dialog/dialog-content.svelte @@ -0,0 +1,48 @@ + + + + + + {@render children?.()} + {#if showCloseButton} + + {#snippet child({ props })} + + {/snippet} + + {/if} + + diff --git a/web/src/lib/components/ui/dialog/dialog-description.svelte b/web/src/lib/components/ui/dialog/dialog-description.svelte new file mode 100644 index 0000000..0102d91 --- /dev/null +++ b/web/src/lib/components/ui/dialog/dialog-description.svelte @@ -0,0 +1,17 @@ + + + diff --git a/web/src/lib/components/ui/dialog/dialog-footer.svelte b/web/src/lib/components/ui/dialog/dialog-footer.svelte new file mode 100644 index 0000000..e23160a --- /dev/null +++ b/web/src/lib/components/ui/dialog/dialog-footer.svelte @@ -0,0 +1,32 @@ + + +
+ {@render children?.()} + {#if showCloseButton} + + {#snippet child({ props })} + + {/snippet} + + {/if} +
diff --git a/web/src/lib/components/ui/dialog/dialog-header.svelte b/web/src/lib/components/ui/dialog/dialog-header.svelte new file mode 100644 index 0000000..c3ce8a2 --- /dev/null +++ b/web/src/lib/components/ui/dialog/dialog-header.svelte @@ -0,0 +1,20 @@ + + +
+ {@render children?.()} +
diff --git a/web/src/lib/components/ui/dialog/dialog-overlay.svelte b/web/src/lib/components/ui/dialog/dialog-overlay.svelte new file mode 100644 index 0000000..19f69f0 --- /dev/null +++ b/web/src/lib/components/ui/dialog/dialog-overlay.svelte @@ -0,0 +1,17 @@ + + + diff --git a/web/src/lib/components/ui/dialog/dialog-portal.svelte b/web/src/lib/components/ui/dialog/dialog-portal.svelte new file mode 100644 index 0000000..ccfa79c --- /dev/null +++ b/web/src/lib/components/ui/dialog/dialog-portal.svelte @@ -0,0 +1,7 @@ + + + diff --git a/web/src/lib/components/ui/dialog/dialog-title.svelte b/web/src/lib/components/ui/dialog/dialog-title.svelte new file mode 100644 index 0000000..6ff1a4a --- /dev/null +++ b/web/src/lib/components/ui/dialog/dialog-title.svelte @@ -0,0 +1,17 @@ + + + diff --git a/web/src/lib/components/ui/dialog/dialog-trigger.svelte b/web/src/lib/components/ui/dialog/dialog-trigger.svelte new file mode 100644 index 0000000..589ee0c --- /dev/null +++ b/web/src/lib/components/ui/dialog/dialog-trigger.svelte @@ -0,0 +1,11 @@ + + + diff --git a/web/src/lib/components/ui/dialog/dialog.svelte b/web/src/lib/components/ui/dialog/dialog.svelte new file mode 100644 index 0000000..211672c --- /dev/null +++ b/web/src/lib/components/ui/dialog/dialog.svelte @@ -0,0 +1,7 @@ + + + diff --git a/web/src/lib/components/ui/dialog/index.ts b/web/src/lib/components/ui/dialog/index.ts new file mode 100644 index 0000000..076cef5 --- /dev/null +++ b/web/src/lib/components/ui/dialog/index.ts @@ -0,0 +1,34 @@ +import Root from "./dialog.svelte"; +import Portal from "./dialog-portal.svelte"; +import Title from "./dialog-title.svelte"; +import Footer from "./dialog-footer.svelte"; +import Header from "./dialog-header.svelte"; +import Overlay from "./dialog-overlay.svelte"; +import Content from "./dialog-content.svelte"; +import Description from "./dialog-description.svelte"; +import Trigger from "./dialog-trigger.svelte"; +import Close from "./dialog-close.svelte"; + +export { + Root, + Title, + Portal, + Footer, + Header, + Trigger, + Overlay, + Content, + Description, + Close, + // + Root as Dialog, + Title as DialogTitle, + Portal as DialogPortal, + Footer as DialogFooter, + Header as DialogHeader, + Trigger as DialogTrigger, + Overlay as DialogOverlay, + Content as DialogContent, + Description as DialogDescription, + Close as DialogClose, +}; diff --git a/web/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-group.svelte b/web/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-group.svelte new file mode 100644 index 0000000..e0e1971 --- /dev/null +++ b/web/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-group.svelte @@ -0,0 +1,16 @@ + + + diff --git a/web/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte b/web/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte new file mode 100644 index 0000000..a81b48d --- /dev/null +++ b/web/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte @@ -0,0 +1,44 @@ + + + + {#snippet children({ checked, indeterminate })} + + {#if indeterminate} + + {:else if checked} + + {/if} + + {@render childrenProp?.()} + {/snippet} + diff --git a/web/src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte b/web/src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte new file mode 100644 index 0000000..261a5b0 --- /dev/null +++ b/web/src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte @@ -0,0 +1,31 @@ + + + + + diff --git a/web/src/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte b/web/src/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte new file mode 100644 index 0000000..433540f --- /dev/null +++ b/web/src/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte @@ -0,0 +1,22 @@ + + + diff --git a/web/src/lib/components/ui/dropdown-menu/dropdown-menu-group.svelte b/web/src/lib/components/ui/dropdown-menu/dropdown-menu-group.svelte new file mode 100644 index 0000000..aca1f7b --- /dev/null +++ b/web/src/lib/components/ui/dropdown-menu/dropdown-menu-group.svelte @@ -0,0 +1,7 @@ + + + diff --git a/web/src/lib/components/ui/dropdown-menu/dropdown-menu-item.svelte b/web/src/lib/components/ui/dropdown-menu/dropdown-menu-item.svelte new file mode 100644 index 0000000..c425190 --- /dev/null +++ b/web/src/lib/components/ui/dropdown-menu/dropdown-menu-item.svelte @@ -0,0 +1,27 @@ + + + diff --git a/web/src/lib/components/ui/dropdown-menu/dropdown-menu-label.svelte b/web/src/lib/components/ui/dropdown-menu/dropdown-menu-label.svelte new file mode 100644 index 0000000..e0c534f --- /dev/null +++ b/web/src/lib/components/ui/dropdown-menu/dropdown-menu-label.svelte @@ -0,0 +1,24 @@ + + +
+ {@render children?.()} +
diff --git a/web/src/lib/components/ui/dropdown-menu/dropdown-menu-portal.svelte b/web/src/lib/components/ui/dropdown-menu/dropdown-menu-portal.svelte new file mode 100644 index 0000000..274cfef --- /dev/null +++ b/web/src/lib/components/ui/dropdown-menu/dropdown-menu-portal.svelte @@ -0,0 +1,7 @@ + + + diff --git a/web/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte b/web/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte new file mode 100644 index 0000000..189aef4 --- /dev/null +++ b/web/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte @@ -0,0 +1,16 @@ + + + diff --git a/web/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte b/web/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte new file mode 100644 index 0000000..c8aa07b --- /dev/null +++ b/web/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte @@ -0,0 +1,34 @@ + + + + {#snippet children({ checked })} + + {#if checked} + + {/if} + + {@render childrenProp?.({ checked })} + {/snippet} + diff --git a/web/src/lib/components/ui/dropdown-menu/dropdown-menu-separator.svelte b/web/src/lib/components/ui/dropdown-menu/dropdown-menu-separator.svelte new file mode 100644 index 0000000..90f1b6f --- /dev/null +++ b/web/src/lib/components/ui/dropdown-menu/dropdown-menu-separator.svelte @@ -0,0 +1,17 @@ + + + diff --git a/web/src/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte b/web/src/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte new file mode 100644 index 0000000..ed7cc85 --- /dev/null +++ b/web/src/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte @@ -0,0 +1,20 @@ + + + + {@render children?.()} + diff --git a/web/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte b/web/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte new file mode 100644 index 0000000..b5750d4 --- /dev/null +++ b/web/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte @@ -0,0 +1,17 @@ + + + diff --git a/web/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte b/web/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte new file mode 100644 index 0000000..fab0275 --- /dev/null +++ b/web/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte @@ -0,0 +1,29 @@ + + + + {@render children?.()} + + diff --git a/web/src/lib/components/ui/dropdown-menu/dropdown-menu-sub.svelte b/web/src/lib/components/ui/dropdown-menu/dropdown-menu-sub.svelte new file mode 100644 index 0000000..f044581 --- /dev/null +++ b/web/src/lib/components/ui/dropdown-menu/dropdown-menu-sub.svelte @@ -0,0 +1,7 @@ + + + diff --git a/web/src/lib/components/ui/dropdown-menu/dropdown-menu-trigger.svelte b/web/src/lib/components/ui/dropdown-menu/dropdown-menu-trigger.svelte new file mode 100644 index 0000000..cb05344 --- /dev/null +++ b/web/src/lib/components/ui/dropdown-menu/dropdown-menu-trigger.svelte @@ -0,0 +1,7 @@ + + + diff --git a/web/src/lib/components/ui/dropdown-menu/dropdown-menu.svelte b/web/src/lib/components/ui/dropdown-menu/dropdown-menu.svelte new file mode 100644 index 0000000..cb4bc62 --- /dev/null +++ b/web/src/lib/components/ui/dropdown-menu/dropdown-menu.svelte @@ -0,0 +1,7 @@ + + + diff --git a/web/src/lib/components/ui/dropdown-menu/index.ts b/web/src/lib/components/ui/dropdown-menu/index.ts new file mode 100644 index 0000000..7850c6a --- /dev/null +++ b/web/src/lib/components/ui/dropdown-menu/index.ts @@ -0,0 +1,54 @@ +import Root from "./dropdown-menu.svelte"; +import Sub from "./dropdown-menu-sub.svelte"; +import CheckboxGroup from "./dropdown-menu-checkbox-group.svelte"; +import CheckboxItem from "./dropdown-menu-checkbox-item.svelte"; +import Content from "./dropdown-menu-content.svelte"; +import Group from "./dropdown-menu-group.svelte"; +import Item from "./dropdown-menu-item.svelte"; +import Label from "./dropdown-menu-label.svelte"; +import RadioGroup from "./dropdown-menu-radio-group.svelte"; +import RadioItem from "./dropdown-menu-radio-item.svelte"; +import Separator from "./dropdown-menu-separator.svelte"; +import Shortcut from "./dropdown-menu-shortcut.svelte"; +import Trigger from "./dropdown-menu-trigger.svelte"; +import SubContent from "./dropdown-menu-sub-content.svelte"; +import SubTrigger from "./dropdown-menu-sub-trigger.svelte"; +import GroupHeading from "./dropdown-menu-group-heading.svelte"; +import Portal from "./dropdown-menu-portal.svelte"; + +export { + CheckboxGroup, + CheckboxItem, + Content, + Portal, + Root as DropdownMenu, + CheckboxGroup as DropdownMenuCheckboxGroup, + CheckboxItem as DropdownMenuCheckboxItem, + Content as DropdownMenuContent, + Portal as DropdownMenuPortal, + Group as DropdownMenuGroup, + Item as DropdownMenuItem, + Label as DropdownMenuLabel, + RadioGroup as DropdownMenuRadioGroup, + RadioItem as DropdownMenuRadioItem, + Separator as DropdownMenuSeparator, + Shortcut as DropdownMenuShortcut, + Sub as DropdownMenuSub, + SubContent as DropdownMenuSubContent, + SubTrigger as DropdownMenuSubTrigger, + Trigger as DropdownMenuTrigger, + GroupHeading as DropdownMenuGroupHeading, + Group, + GroupHeading, + Item, + Label, + RadioGroup, + RadioItem, + Root, + Separator, + Shortcut, + Sub, + SubContent, + SubTrigger, + Trigger, +}; diff --git a/web/src/lib/components/ui/input/index.ts b/web/src/lib/components/ui/input/index.ts index f55e99c..f47b6d3 100644 --- a/web/src/lib/components/ui/input/index.ts +++ b/web/src/lib/components/ui/input/index.ts @@ -1,2 +1,7 @@ -import Root from './input.svelte'; -export { Root, Root as Input }; +import Root from "./input.svelte"; + +export { + Root, + // + Root as Input, +}; diff --git a/web/src/lib/components/ui/input/input.svelte b/web/src/lib/components/ui/input/input.svelte index ca430ed..fe7db38 100644 --- a/web/src/lib/components/ui/input/input.svelte +++ b/web/src/lib/components/ui/input/input.svelte @@ -1,19 +1,48 @@ - +{#if type === "file"} + +{:else} + +{/if} diff --git a/web/src/lib/components/ui/label/index.ts b/web/src/lib/components/ui/label/index.ts index 2543553..8bfca0b 100644 --- a/web/src/lib/components/ui/label/index.ts +++ b/web/src/lib/components/ui/label/index.ts @@ -1,2 +1,7 @@ -import Root from './label.svelte'; -export { Root, Root as Label }; +import Root from "./label.svelte"; + +export { + Root, + // + Root as Label, +}; diff --git a/web/src/lib/components/ui/label/label.svelte b/web/src/lib/components/ui/label/label.svelte index 407823c..d5e3086 100644 --- a/web/src/lib/components/ui/label/label.svelte +++ b/web/src/lib/components/ui/label/label.svelte @@ -1,15 +1,20 @@ - + diff --git a/web/src/lib/components/ui/popover/index.ts b/web/src/lib/components/ui/popover/index.ts new file mode 100644 index 0000000..5f18036 --- /dev/null +++ b/web/src/lib/components/ui/popover/index.ts @@ -0,0 +1,28 @@ +import Root from "./popover.svelte"; +import Close from "./popover-close.svelte"; +import Content from "./popover-content.svelte"; +import Description from "./popover-description.svelte"; +import Header from "./popover-header.svelte"; +import Title from "./popover-title.svelte"; +import Trigger from "./popover-trigger.svelte"; +import Portal from "./popover-portal.svelte"; + +export { + Root, + Content, + Description, + Header, + Title, + Trigger, + Close, + Portal, + // + Root as Popover, + Content as PopoverContent, + Description as PopoverDescription, + Header as PopoverHeader, + Title as PopoverTitle, + Trigger as PopoverTrigger, + Close as PopoverClose, + Portal as PopoverPortal, +}; diff --git a/web/src/lib/components/ui/popover/popover-close.svelte b/web/src/lib/components/ui/popover/popover-close.svelte new file mode 100644 index 0000000..c360925 --- /dev/null +++ b/web/src/lib/components/ui/popover/popover-close.svelte @@ -0,0 +1,7 @@ + + + diff --git a/web/src/lib/components/ui/popover/popover-content.svelte b/web/src/lib/components/ui/popover/popover-content.svelte new file mode 100644 index 0000000..dd9a38f --- /dev/null +++ b/web/src/lib/components/ui/popover/popover-content.svelte @@ -0,0 +1,31 @@ + + + + + diff --git a/web/src/lib/components/ui/popover/popover-description.svelte b/web/src/lib/components/ui/popover/popover-description.svelte new file mode 100644 index 0000000..c28ab23 --- /dev/null +++ b/web/src/lib/components/ui/popover/popover-description.svelte @@ -0,0 +1,20 @@ + + +
+ {@render children?.()} +
diff --git a/web/src/lib/components/ui/popover/popover-header.svelte b/web/src/lib/components/ui/popover/popover-header.svelte new file mode 100644 index 0000000..69b85b1 --- /dev/null +++ b/web/src/lib/components/ui/popover/popover-header.svelte @@ -0,0 +1,20 @@ + + +
+ {@render children?.()} +
diff --git a/web/src/lib/components/ui/popover/popover-portal.svelte b/web/src/lib/components/ui/popover/popover-portal.svelte new file mode 100644 index 0000000..dd8265f --- /dev/null +++ b/web/src/lib/components/ui/popover/popover-portal.svelte @@ -0,0 +1,7 @@ + + + diff --git a/web/src/lib/components/ui/popover/popover-title.svelte b/web/src/lib/components/ui/popover/popover-title.svelte new file mode 100644 index 0000000..ee9f2ff --- /dev/null +++ b/web/src/lib/components/ui/popover/popover-title.svelte @@ -0,0 +1,20 @@ + + +
+ {@render children?.()} +
diff --git a/web/src/lib/components/ui/popover/popover-trigger.svelte b/web/src/lib/components/ui/popover/popover-trigger.svelte new file mode 100644 index 0000000..586323c --- /dev/null +++ b/web/src/lib/components/ui/popover/popover-trigger.svelte @@ -0,0 +1,17 @@ + + + diff --git a/web/src/lib/components/ui/popover/popover.svelte b/web/src/lib/components/ui/popover/popover.svelte new file mode 100644 index 0000000..6b1aa5f --- /dev/null +++ b/web/src/lib/components/ui/popover/popover.svelte @@ -0,0 +1,7 @@ + + + diff --git a/web/src/lib/components/ui/scroll-area/index.ts b/web/src/lib/components/ui/scroll-area/index.ts index bec8204..e86a25b 100644 --- a/web/src/lib/components/ui/scroll-area/index.ts +++ b/web/src/lib/components/ui/scroll-area/index.ts @@ -1,2 +1,10 @@ -import Root from './scroll-area.svelte'; -export { Root, Root as ScrollArea }; +import Scrollbar from "./scroll-area-scrollbar.svelte"; +import Root from "./scroll-area.svelte"; + +export { + Root, + Scrollbar, + //, + Root as ScrollArea, + Scrollbar as ScrollAreaScrollbar, +}; diff --git a/web/src/lib/components/ui/scroll-area/scroll-area-scrollbar.svelte b/web/src/lib/components/ui/scroll-area/scroll-area-scrollbar.svelte new file mode 100644 index 0000000..b9518f3 --- /dev/null +++ b/web/src/lib/components/ui/scroll-area/scroll-area-scrollbar.svelte @@ -0,0 +1,30 @@ + + + + {@render children?.()} + + diff --git a/web/src/lib/components/ui/scroll-area/scroll-area.svelte b/web/src/lib/components/ui/scroll-area/scroll-area.svelte index 20825c1..d4d96d0 100644 --- a/web/src/lib/components/ui/scroll-area/scroll-area.svelte +++ b/web/src/lib/components/ui/scroll-area/scroll-area.svelte @@ -1,12 +1,43 @@ -
{@render children?.()}
+ + + {@render children?.()} + + {#if orientation === "vertical" || orientation === "both"} + + {/if} + {#if orientation === "horizontal" || orientation === "both"} + + {/if} + + diff --git a/web/src/lib/components/ui/select/index.ts b/web/src/lib/components/ui/select/index.ts new file mode 100644 index 0000000..4dec358 --- /dev/null +++ b/web/src/lib/components/ui/select/index.ts @@ -0,0 +1,37 @@ +import Root from "./select.svelte"; +import Group from "./select-group.svelte"; +import Label from "./select-label.svelte"; +import Item from "./select-item.svelte"; +import Content from "./select-content.svelte"; +import Trigger from "./select-trigger.svelte"; +import Separator from "./select-separator.svelte"; +import ScrollDownButton from "./select-scroll-down-button.svelte"; +import ScrollUpButton from "./select-scroll-up-button.svelte"; +import GroupHeading from "./select-group-heading.svelte"; +import Portal from "./select-portal.svelte"; + +export { + Root, + Group, + Label, + Item, + Content, + Trigger, + Separator, + ScrollDownButton, + ScrollUpButton, + GroupHeading, + Portal, + // + Root as Select, + Group as SelectGroup, + Label as SelectLabel, + Item as SelectItem, + Content as SelectContent, + Trigger as SelectTrigger, + Separator as SelectSeparator, + ScrollDownButton as SelectScrollDownButton, + ScrollUpButton as SelectScrollUpButton, + GroupHeading as SelectGroupHeading, + Portal as SelectPortal, +}; diff --git a/web/src/lib/components/ui/select/select-content.svelte b/web/src/lib/components/ui/select/select-content.svelte new file mode 100644 index 0000000..bb1e2ac --- /dev/null +++ b/web/src/lib/components/ui/select/select-content.svelte @@ -0,0 +1,45 @@ + + + + + + + {@render children?.()} + + + + diff --git a/web/src/lib/components/ui/select/select-group-heading.svelte b/web/src/lib/components/ui/select/select-group-heading.svelte new file mode 100644 index 0000000..1fab5f0 --- /dev/null +++ b/web/src/lib/components/ui/select/select-group-heading.svelte @@ -0,0 +1,21 @@ + + + + {@render children?.()} + diff --git a/web/src/lib/components/ui/select/select-group.svelte b/web/src/lib/components/ui/select/select-group.svelte new file mode 100644 index 0000000..f666cb2 --- /dev/null +++ b/web/src/lib/components/ui/select/select-group.svelte @@ -0,0 +1,17 @@ + + + diff --git a/web/src/lib/components/ui/select/select-item.svelte b/web/src/lib/components/ui/select/select-item.svelte new file mode 100644 index 0000000..32fd5ce --- /dev/null +++ b/web/src/lib/components/ui/select/select-item.svelte @@ -0,0 +1,38 @@ + + + + {#snippet children({ selected, highlighted })} + + {#if selected} + + {/if} + + {#if childrenProp} + {@render childrenProp({ selected, highlighted })} + {:else} + {label || value} + {/if} + {/snippet} + diff --git a/web/src/lib/components/ui/select/select-label.svelte b/web/src/lib/components/ui/select/select-label.svelte new file mode 100644 index 0000000..69bcfdf --- /dev/null +++ b/web/src/lib/components/ui/select/select-label.svelte @@ -0,0 +1,20 @@ + + +
+ {@render children?.()} +
diff --git a/web/src/lib/components/ui/select/select-portal.svelte b/web/src/lib/components/ui/select/select-portal.svelte new file mode 100644 index 0000000..424bcdd --- /dev/null +++ b/web/src/lib/components/ui/select/select-portal.svelte @@ -0,0 +1,7 @@ + + + diff --git a/web/src/lib/components/ui/select/select-scroll-down-button.svelte b/web/src/lib/components/ui/select/select-scroll-down-button.svelte new file mode 100644 index 0000000..94f41cd --- /dev/null +++ b/web/src/lib/components/ui/select/select-scroll-down-button.svelte @@ -0,0 +1,20 @@ + + + + + diff --git a/web/src/lib/components/ui/select/select-scroll-up-button.svelte b/web/src/lib/components/ui/select/select-scroll-up-button.svelte new file mode 100644 index 0000000..035ea09 --- /dev/null +++ b/web/src/lib/components/ui/select/select-scroll-up-button.svelte @@ -0,0 +1,20 @@ + + + + + diff --git a/web/src/lib/components/ui/select/select-separator.svelte b/web/src/lib/components/ui/select/select-separator.svelte new file mode 100644 index 0000000..3b24bab --- /dev/null +++ b/web/src/lib/components/ui/select/select-separator.svelte @@ -0,0 +1,18 @@ + + + diff --git a/web/src/lib/components/ui/select/select-trigger.svelte b/web/src/lib/components/ui/select/select-trigger.svelte new file mode 100644 index 0000000..03d06d0 --- /dev/null +++ b/web/src/lib/components/ui/select/select-trigger.svelte @@ -0,0 +1,29 @@ + + + + {@render children?.()} + + diff --git a/web/src/lib/components/ui/select/select.svelte b/web/src/lib/components/ui/select/select.svelte new file mode 100644 index 0000000..05eb663 --- /dev/null +++ b/web/src/lib/components/ui/select/select.svelte @@ -0,0 +1,11 @@ + + + diff --git a/web/src/lib/components/ui/separator/index.ts b/web/src/lib/components/ui/separator/index.ts index 18cdeac..82442d2 100644 --- a/web/src/lib/components/ui/separator/index.ts +++ b/web/src/lib/components/ui/separator/index.ts @@ -1,2 +1,7 @@ -import Root from './separator.svelte'; -export { Root, Root as Separator }; +import Root from "./separator.svelte"; + +export { + Root, + // + Root as Separator, +}; diff --git a/web/src/lib/components/ui/separator/separator.svelte b/web/src/lib/components/ui/separator/separator.svelte index e42befd..5fd8a42 100644 --- a/web/src/lib/components/ui/separator/separator.svelte +++ b/web/src/lib/components/ui/separator/separator.svelte @@ -1,20 +1,23 @@ - + {...restProps} +/> diff --git a/web/src/lib/components/ui/switch/index.ts b/web/src/lib/components/ui/switch/index.ts new file mode 100644 index 0000000..f5533db --- /dev/null +++ b/web/src/lib/components/ui/switch/index.ts @@ -0,0 +1,7 @@ +import Root from "./switch.svelte"; + +export { + Root, + // + Root as Switch, +}; diff --git a/web/src/lib/components/ui/switch/switch.svelte b/web/src/lib/components/ui/switch/switch.svelte new file mode 100644 index 0000000..6aa57d5 --- /dev/null +++ b/web/src/lib/components/ui/switch/switch.svelte @@ -0,0 +1,31 @@ + + + + + diff --git a/web/src/lib/components/ui/table/index.ts b/web/src/lib/components/ui/table/index.ts index d80b74d..14695c8 100644 --- a/web/src/lib/components/ui/table/index.ts +++ b/web/src/lib/components/ui/table/index.ts @@ -1,16 +1,28 @@ -import Root from './table.svelte'; -import Header from './table-header.svelte'; -import Body from './table-body.svelte'; -import Row from './table-row.svelte'; -import Head from './table-head.svelte'; -import Cell from './table-cell.svelte'; +import Root from "./table.svelte"; +import Body from "./table-body.svelte"; +import Caption from "./table-caption.svelte"; +import Cell from "./table-cell.svelte"; +import Footer from "./table-footer.svelte"; +import Head from "./table-head.svelte"; +import Header from "./table-header.svelte"; +import Row from "./table-row.svelte"; export { Root, + Body, + Caption, + Cell, + Footer, + Head, + Header, + Row, + // Root as Table, - Header as TableHeader, Body as TableBody, - Row as TableRow, + Caption as TableCaption, + Cell as TableCell, + Footer as TableFooter, Head as TableHead, - Cell as TableCell + Header as TableHeader, + Row as TableRow, }; diff --git a/web/src/lib/components/ui/table/table-body.svelte b/web/src/lib/components/ui/table/table-body.svelte index 6d8168d..935feae 100644 --- a/web/src/lib/components/ui/table/table-body.svelte +++ b/web/src/lib/components/ui/table/table-body.svelte @@ -1,12 +1,15 @@ -{@render children?.()} + + {@render children?.()} + diff --git a/web/src/lib/components/ui/table/table-caption.svelte b/web/src/lib/components/ui/table/table-caption.svelte new file mode 100644 index 0000000..4696cff --- /dev/null +++ b/web/src/lib/components/ui/table/table-caption.svelte @@ -0,0 +1,20 @@ + + + + {@render children?.()} + diff --git a/web/src/lib/components/ui/table/table-cell.svelte b/web/src/lib/components/ui/table/table-cell.svelte index c6283bd..a998bf6 100644 --- a/web/src/lib/components/ui/table/table-cell.svelte +++ b/web/src/lib/components/ui/table/table-cell.svelte @@ -1,23 +1,15 @@ -[role=checkbox]]:translate-y-[2px]', className)} - {colspan} - {rowspan} - {...rest}>{@render children?.()} + + {@render children?.()} + diff --git a/web/src/lib/components/ui/table/table-footer.svelte b/web/src/lib/components/ui/table/table-footer.svelte new file mode 100644 index 0000000..b9b14eb --- /dev/null +++ b/web/src/lib/components/ui/table/table-footer.svelte @@ -0,0 +1,20 @@ + + +tr]:last:border-b-0", className)} + {...restProps} +> + {@render children?.()} + diff --git a/web/src/lib/components/ui/table/table-head.svelte b/web/src/lib/components/ui/table/table-head.svelte index d9eaec8..267c4e0 100644 --- a/web/src/lib/components/ui/table/table-head.svelte +++ b/web/src/lib/components/ui/table/table-head.svelte @@ -1,18 +1,15 @@ -[role=checkbox]]:translate-y-[2px]', - className - )} - {...rest}>{@render children?.()} + + {@render children?.()} + diff --git a/web/src/lib/components/ui/table/table-header.svelte b/web/src/lib/components/ui/table/table-header.svelte index 0814de1..f47d259 100644 --- a/web/src/lib/components/ui/table/table-header.svelte +++ b/web/src/lib/components/ui/table/table-header.svelte @@ -1,12 +1,20 @@ -{@render children?.()} + + {@render children?.()} + diff --git a/web/src/lib/components/ui/table/table-row.svelte b/web/src/lib/components/ui/table/table-row.svelte index 6dc7c3d..90b4e2a 100644 --- a/web/src/lib/components/ui/table/table-row.svelte +++ b/web/src/lib/components/ui/table/table-row.svelte @@ -1,15 +1,15 @@ -{@render children?.()} + + {@render children?.()} + diff --git a/web/src/lib/components/ui/table/table.svelte b/web/src/lib/components/ui/table/table.svelte index 0d629ca..d95a02e 100644 --- a/web/src/lib/components/ui/table/table.svelte +++ b/web/src/lib/components/ui/table/table.svelte @@ -1,14 +1,17 @@ -
- {@render children?.()}
+
+ + {@render children?.()} +
diff --git a/web/src/lib/components/ui/tabs/index.ts b/web/src/lib/components/ui/tabs/index.ts new file mode 100644 index 0000000..31267e5 --- /dev/null +++ b/web/src/lib/components/ui/tabs/index.ts @@ -0,0 +1,18 @@ +import Root from "./tabs.svelte"; +import Content from "./tabs-content.svelte"; +import List, { tabsListVariants, type TabsListVariant } from "./tabs-list.svelte"; +import Trigger from "./tabs-trigger.svelte"; + +export { + Root, + Content, + List, + Trigger, + tabsListVariants, + type TabsListVariant, + // + Root as Tabs, + Content as TabsContent, + List as TabsList, + Trigger as TabsTrigger, +}; diff --git a/web/src/lib/components/ui/tabs/tabs-content.svelte b/web/src/lib/components/ui/tabs/tabs-content.svelte new file mode 100644 index 0000000..394ab3e --- /dev/null +++ b/web/src/lib/components/ui/tabs/tabs-content.svelte @@ -0,0 +1,17 @@ + + + diff --git a/web/src/lib/components/ui/tabs/tabs-list.svelte b/web/src/lib/components/ui/tabs/tabs-list.svelte new file mode 100644 index 0000000..18dce00 --- /dev/null +++ b/web/src/lib/components/ui/tabs/tabs-list.svelte @@ -0,0 +1,40 @@ + + + + + diff --git a/web/src/lib/components/ui/tabs/tabs-trigger.svelte b/web/src/lib/components/ui/tabs/tabs-trigger.svelte new file mode 100644 index 0000000..8a0be4a --- /dev/null +++ b/web/src/lib/components/ui/tabs/tabs-trigger.svelte @@ -0,0 +1,23 @@ + + + diff --git a/web/src/lib/components/ui/tabs/tabs.svelte b/web/src/lib/components/ui/tabs/tabs.svelte new file mode 100644 index 0000000..bfb900d --- /dev/null +++ b/web/src/lib/components/ui/tabs/tabs.svelte @@ -0,0 +1,19 @@ + + + diff --git a/web/src/lib/components/ui/textarea/index.ts b/web/src/lib/components/ui/textarea/index.ts new file mode 100644 index 0000000..ace797a --- /dev/null +++ b/web/src/lib/components/ui/textarea/index.ts @@ -0,0 +1,7 @@ +import Root from "./textarea.svelte"; + +export { + Root, + // + Root as Textarea, +}; diff --git a/web/src/lib/components/ui/textarea/textarea.svelte b/web/src/lib/components/ui/textarea/textarea.svelte new file mode 100644 index 0000000..2e779ff --- /dev/null +++ b/web/src/lib/components/ui/textarea/textarea.svelte @@ -0,0 +1,23 @@ + + + diff --git a/web/src/lib/components/ui/tooltip/index.ts b/web/src/lib/components/ui/tooltip/index.ts new file mode 100644 index 0000000..1718604 --- /dev/null +++ b/web/src/lib/components/ui/tooltip/index.ts @@ -0,0 +1,19 @@ +import Root from "./tooltip.svelte"; +import Trigger from "./tooltip-trigger.svelte"; +import Content from "./tooltip-content.svelte"; +import Provider from "./tooltip-provider.svelte"; +import Portal from "./tooltip-portal.svelte"; + +export { + Root, + Trigger, + Content, + Provider, + Portal, + // + Root as Tooltip, + Content as TooltipContent, + Trigger as TooltipTrigger, + Provider as TooltipProvider, + Portal as TooltipPortal, +}; diff --git a/web/src/lib/components/ui/tooltip/tooltip-content.svelte b/web/src/lib/components/ui/tooltip/tooltip-content.svelte new file mode 100644 index 0000000..0cf0694 --- /dev/null +++ b/web/src/lib/components/ui/tooltip/tooltip-content.svelte @@ -0,0 +1,52 @@ + + + + + {@render children?.()} + + {#snippet child({ props })} +
+ {/snippet} +
+
+
diff --git a/web/src/lib/components/ui/tooltip/tooltip-portal.svelte b/web/src/lib/components/ui/tooltip/tooltip-portal.svelte new file mode 100644 index 0000000..d234f7d --- /dev/null +++ b/web/src/lib/components/ui/tooltip/tooltip-portal.svelte @@ -0,0 +1,7 @@ + + + diff --git a/web/src/lib/components/ui/tooltip/tooltip-provider.svelte b/web/src/lib/components/ui/tooltip/tooltip-provider.svelte new file mode 100644 index 0000000..6dba9a6 --- /dev/null +++ b/web/src/lib/components/ui/tooltip/tooltip-provider.svelte @@ -0,0 +1,7 @@ + + + diff --git a/web/src/lib/components/ui/tooltip/tooltip-trigger.svelte b/web/src/lib/components/ui/tooltip/tooltip-trigger.svelte new file mode 100644 index 0000000..1acdaa4 --- /dev/null +++ b/web/src/lib/components/ui/tooltip/tooltip-trigger.svelte @@ -0,0 +1,7 @@ + + + diff --git a/web/src/lib/components/ui/tooltip/tooltip.svelte b/web/src/lib/components/ui/tooltip/tooltip.svelte new file mode 100644 index 0000000..44bee9f --- /dev/null +++ b/web/src/lib/components/ui/tooltip/tooltip.svelte @@ -0,0 +1,10 @@ + + + + + diff --git a/web/src/routes/+page.svelte b/web/src/routes/+page.svelte index bc28a43..887395f 100644 --- a/web/src/routes/+page.svelte +++ b/web/src/routes/+page.svelte @@ -1,97 +1,121 @@ -
+

Обзор

-

- Краткая сводка по API. Укажите токен в разделе «Настройки», если запросы к защищённым путям - возвращают 401. -

+

Состояние EvoBGP control plane.

-
- - - Health - GET /v1/health - - - {#if health === 'ok'} - ok - {:else if health === 'err'} - недоступно - {:else} - проверка… - {/if} - - + + + + {#if healthy === null} +
+ Проверка… + {:else if healthy} + + API работает + {:else} + + API недоступен + {/if} +
+
- - - Модули - GET /v1/modules - - -

{modulesN}

-
-
- - - - Ревизии - GET /v1/revisions - - -

{revN}

-
-
- - - - Пиры - GET /v1/peers - - -

{peersN}

-
-
+ +
+ {#each stats as stat (stat.label)} + {@const Icon = stat.icon} + + +
+ + + {stat.label} + + +
+ + {loading ? '—' : stat.value} + +
+ +

{stat.description}

+
+
+ {/each}
+ + + + + Быстрые действия + + + + + + + + +
diff --git a/web/src/routes/directories/+page.svelte b/web/src/routes/directories/+page.svelte new file mode 100644 index 0000000..3d0779a --- /dev/null +++ b/web/src/routes/directories/+page.svelte @@ -0,0 +1,372 @@ + + +
+
+

Справочники

+

BGP Communities и DoH-профили для резолвинга доменов.

+
+ + + + Communities + DoH профили + + + + + + +
+ BGP Communities + Используются для тегирования префиксов +
+
+ + +
+
+ + + + + Название + Вид + ID + + + + + {#each communities as c (c.id)} + + {c.name} + + {#if c.kind} + {c.kind} + {:else} + + {/if} + + {c.id} + +
+ + +
+
+
+ {:else} + + + {commLoading ? 'Загрузка…' : 'Нет communities. Создайте первую.'} + + + {/each} +
+
+
+
+
+ + + + + +
+ DoH профили + DNS-over-HTTPS серверы для резолвинга доменных модулей +
+
+ + +
+
+ + + + + URL + Таймаут (мс) + ID + + + + + {#each dohProfiles as d (d.id)} + + {d.url} + {d.timeout_ms ?? '—'} + {d.id} + +
+ + +
+
+
+ {:else} + + + {dohLoading ? 'Загрузка…' : 'Нет DoH профилей.'} + + + {/each} +
+
+
+
+
+
+
+ + + + + + {commEdit ? 'Редактировать' : 'Новая'} community + +
+
+ + +
+
+ + +
+
+ + + + +
+
+ + { if (!v) commDeleteTarget = null; }}> + + + Удалить community «{commDeleteTarget?.name}»? + Это приведёт к удалению привязки во всех модулях. + + + (commDeleteTarget = null)}>Отмена + Удалить + + + + + + + + + {dohEdit ? 'Редактировать' : 'Новый'} DoH профиль + +
+
+ + +
+
+ + +
+
+ + + + +
+
+ + { if (!v) dohDeleteTarget = null; }}> + + + Удалить DoH профиль? + {dohDeleteTarget?.url} + + + (dohDeleteTarget = null)}>Отмена + Удалить + + + diff --git a/web/src/routes/modules/+page.svelte b/web/src/routes/modules/+page.svelte index 0f5cd5a..2ea48db 100644 --- a/web/src/routes/modules/+page.svelte +++ b/web/src/routes/modules/+page.svelte @@ -1,10 +1,27 @@ -
-
-

Модули префиксов

-

Список экземпляров модулей tenant (из API).

+
+
+
+

Модули префиксов

+

Управление модулями — AS, CDN, домены, IP-диапазоны.

+
+
+ + +
- - Модули - GET /v1/modules - - - {#if err} -

{err}

- {:else} - - - - - Имя - Тип - Приоритет - Статус - - - - {#each rows as m} - - {m.name} - - {m.type} - - {m.priority} - - {#if m.enabled} - вкл - {:else} - выкл - {/if} - - - {:else} - - Нет данных - - {/each} - -
-
- {/if} + + + + + Название + Тип + Приоритет + Интервал + Статус + + + + + {#each rows as m (m.id)} + + {m.name} + + {m.type} + + {m.priority} + + {m.cron_expr ?? (m.refresh_interval_sec ? `${m.refresh_interval_sec}с` : '—')} + + + {#if m.enabled} + вкл + {:else} + выкл + {/if} + + + + + + {:else} + + + {loading ? 'Загрузка…' : 'Нет модулей. Создайте первый.'} + + + {/each} + +
+ + + + + + Новый модуль + Создание нового модуля префиксов. + +
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+
+
+ + +
+
+ + + + +
+
diff --git a/web/src/routes/modules/[moduleId]/+page.svelte b/web/src/routes/modules/[moduleId]/+page.svelte new file mode 100644 index 0000000..e8a70e1 --- /dev/null +++ b/web/src/routes/modules/[moduleId]/+page.svelte @@ -0,0 +1,909 @@ + + +{#if loadingMod} +
Загрузка…
+{:else if mod} +
+ +
+
+ +
+
+

{mod.name}

+ {mod.type} + {#if mod.enabled} + вкл + {:else} + выкл + {/if} +
+

{mod.id}

+
+
+
+ + + +
+
+ + +
+ +

Приоритет

+

{mod.priority}

+
+ +

Интервал

+

+ {mod.cron_expr ?? (mod.refresh_interval_sec ? `${mod.refresh_interval_sec}с` : '—')} +

+
+ +

DoH профиль

+

{mod.doh_profile_id ? mod.doh_profile_id.slice(0, 8) + '…' : '—'}

+
+ +

Community по умолч.

+

{communityName(mod.default_community_id)}

+
+
+ + + {#if mod.type === 'AS_PREFIXES'} + + +
+ AS-записи + ASN и/или префиксы для анонса +
+ +
+ + + + + ASN + Префикс + Community + + + + + {#each asEntries as entry (entry.id)} + + {entry.asn ?? '—'} + {entry.prefix ?? '—'} + {communityName(entry.community_id)} + +
+ + +
+
+
+ {:else} + + Нет записей + + {/each} +
+
+
+
+ {:else if mod.type === 'CDN_CIDRS'} + + +
+ CDN-источники + URL источников для скачивания списков CIDR +
+ +
+ + + + + URL + Тип + Community + Интервал + + + + + {#each cdnSources as src (src.id)} + + {src.url} + {src.source_kind} + {communityName(src.community_id)} + {src.refresh_interval_sec ? `${src.refresh_interval_sec}с` : '—'} + +
+ + +
+
+
+ {:else} + + Нет источников + + {/each} +
+
+
+
+ {:else if mod.type === 'DOMAINS'} + + +
+ Домены + FQDN для резолвинга через DoH +
+ +
+ + + + + FQDN + Community + + + + + {#each domainEntries as entry (entry.id)} + + {entry.fqdn} + {communityName(entry.community_id)} + +
+ + +
+
+
+ {:else} + + Нет доменов + + {/each} +
+
+
+
+ {:else if mod.type === 'IP_RANGES'} + + +
+ IP-диапазоны + Статические CIDR для анонса +
+ +
+ + + + + Префикс (CIDR) + Community + + + + + {#each ipEntries as entry (entry.id)} + + {entry.prefix} + {communityName(entry.community_id)} + +
+ + +
+
+
+ {:else} + + Нет диапазонов + + {/each} +
+
+
+
+ {/if} +
+{:else} +

Модуль не найден

+{/if} + + + + + + Редактировать модуль + +
+
+ + +
+
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + + + +
+
+ + + + + + Удалить модуль «{mod?.name}»? + Это действие необратимо. Модуль и все его записи будут удалены. + + + Отмена + + {deletingMod ? 'Удаление…' : 'Удалить'} + + + + + + + + + + {asEdit ? 'Редактировать запись' : 'Новая AS-запись'} + ASN и/или префикс для анонса через BGP. + +
+
+ + +
+
+ + +
+
+ + +
+
+ + + + +
+
+ + { if (!v) asDeleteTarget = null; }}> + + + Удалить запись? + ASN: {asDeleteTarget?.asn ?? '—'}, Префикс: {asDeleteTarget?.prefix ?? '—'} + + + (asDeleteTarget = null)}>Отмена + Удалить + + + + + + + + + {cdnEdit ? 'Редактировать источник' : 'Новый CDN-источник'} + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + + + +
+
+ + { if (!v) cdnDeleteTarget = null; }}> + + + Удалить CDN-источник? + {cdnDeleteTarget?.url} + + + (cdnDeleteTarget = null)}>Отмена + Удалить + + + + + + + + + {domainEdit ? 'Редактировать домен' : 'Новый домен'} + +
+
+ + +
+
+ + +
+
+ + + + +
+
+ + { if (!v) domainDeleteTarget = null; }}> + + + Удалить домен? + {domainDeleteTarget?.fqdn} + + + (domainDeleteTarget = null)}>Отмена + Удалить + + + + + + + + + {ipEdit ? 'Редактировать диапазон' : 'Новый IP-диапазон'} + +
+
+ + +
+
+ + +
+
+ + + + +
+
+ + { if (!v) ipDeleteTarget = null; }}> + + + Удалить диапазон? + {ipDeleteTarget?.prefix} + + + (ipDeleteTarget = null)}>Отмена + Удалить + + + diff --git a/web/src/routes/modules/[moduleId]/+page.ts b/web/src/routes/modules/[moduleId]/+page.ts new file mode 100644 index 0000000..d43d0cd --- /dev/null +++ b/web/src/routes/modules/[moduleId]/+page.ts @@ -0,0 +1 @@ +export const prerender = false; diff --git a/web/src/routes/monitoring/+page.svelte b/web/src/routes/monitoring/+page.svelte index 75d6e72..f23c967 100644 --- a/web/src/routes/monitoring/+page.svelte +++ b/web/src/routes/monitoring/+page.svelte @@ -1,148 +1,122 @@
-
-

Мониторинг

-

- Публичные системные эндпоинты и срез Prometheus-метрик (первые строки). Полный scrape обычно делает - Prometheus. -

+
+
+

Мониторинг

+

Доступность API и версия сборки.

+
+
-
+
+ - Health + Liveness GET /v1/health - - {#if healthOk === true} - OK - {:else if healthOk === false} - ошибка + + {#if health === null} + + {:else if health.ok} + OK + {#if health.status}

{health.status}

{/if} {:else} - + FAIL {/if} -
{healthBody || '—'}
+ - Ready + Readiness GET /v1/ready - {#if ready} -
{JSON.stringify(
-							ready,
-							null,
-							2
-						)}
+ {#if ready === null} + {:else} -

Недоступно

+ {ready.status} + {#if ready.checks} +
+ {#each Object.entries(ready.checks) as [k, v]} +
+ {k} + {JSON.stringify(v)} +
+ {/each} +
+ {/if} {/if}
+ Версия GET /v1/version - {#if version} -
-
-
api_version
-
{version.api_version ?? '—'}
-
-
-
git_sha
-
{version.git_sha ?? '—'}
-
-
+ {#if version === null} + {:else} -

Недоступно

+
+ {#each Object.entries(version) as [k, v]} +
+ {k} + {String(v)} +
+ {/each} +
{/if}
- - - - Метрики - GET /metrics (Prometheus) - - - - {#if metricsErr} -

{metricsErr}

- {/if} - -
{metricsSnippet ||
-							'Нажмите «Загрузить срез»'}
-
-
-
diff --git a/web/src/routes/network/+page.svelte b/web/src/routes/network/+page.svelte new file mode 100644 index 0000000..1106522 --- /dev/null +++ b/web/src/routes/network/+page.svelte @@ -0,0 +1,372 @@ + + +
+
+

Сеть

+

BGP-пиры и спикеры (BIRD-агенты).

+
+ + + + Пиры + Спикеры + + + + + + +
+ BGP-пиры + Настройка BGP-соседей +
+
+ + +
+
+ + + + + Адрес + Remote ASN + Состояние сессии + Спикер + + + + + {#each peers as p (p.id)} + + {p.neighbor} + {p.remote_asn ?? '—'} + + {p.session_state || '—'} + + {p.bgp_speaker_id ? p.bgp_speaker_id.slice(0, 8) + '…' : '—'} + +
+ + +
+
+
+ {:else} + + + {peersLoading ? 'Загрузка…' : 'Нет пиров.'} + + + {/each} +
+
+
+
+
+ + + + + +
+ Спикеры + BIRD-агенты, применяющие конфигурацию +
+
+ + +
+
+ + + + + Endpoint + Роль + Последняя ревизия + + + + + {#each speakers as s (s.id)} + + {s.endpoint} + {s.role} + {s.last_applied_revision_id ? s.last_applied_revision_id.slice(0, 8) + '…' : '—'} + +
+ + +
+
+
+ {:else} + + + {speakersLoading ? 'Загрузка…' : 'Нет спикеров.'} + + + {/each} +
+
+
+
+
+
+
+ + + + + + {peerEdit ? 'Редактировать пира' : 'Новый пир'} + BGP-сосед для установки сессии + +
+
+ + +
+
+ + +
+
+ + +
+
+ + + + +
+
+ + { if (!v) peerDeleteTarget = null; }}> + + + Удалить пира? + {peerDeleteTarget?.neighbor} + + + (peerDeleteTarget = null)}>Отмена + Удалить + + + + + + + + + {speakerEdit ? 'Редактировать спикера' : 'Новый спикер'} + +
+
+ + +
+
+ + +
+
+ + + + +
+
diff --git a/web/src/routes/operations/+page.svelte b/web/src/routes/operations/+page.svelte new file mode 100644 index 0000000..8e22f20 --- /dev/null +++ b/web/src/routes/operations/+page.svelte @@ -0,0 +1,528 @@ + + +
+
+

Операции

+

Деплой конфигурации, управление ревизиями и задачами.

+
+ + +
+ +
+
+

Apply all speakers

+

Применить текущую конфигурацию на всех спикерах

+
+ +
+
+ +
+
+

BIRD Reload

+

Перезагрузить конфигурацию BIRD на всех спикерах

+
+ +
+
+
+ + + + Ревизии + Diff + Задачи + + + + + + +
+ История ревизий + Автоматически создаются при apply +
+ +
+ + + + + ID + Создана + Префиксов + Хэш + + + + + {#each revisions as rev (rev.id)} + + {rev.id.slice(0, 8)}… + {formatDate(rev.created_at)} + {rev.materialized_prefix_count} + {rev.content_hash.slice(0, 12)}… + +
+ + +
+
+
+ {:else} + + + {revLoading ? 'Загрузка…' : 'Нет ревизий'} + + + {/each} +
+
+
+
+
+ + + + + + Сравнение ревизий + Выберите ID двух ревизий для сравнения + + +
+ + + +
+ {#if diffData} +
+
+

+ Добавлено ({diffData.added?.length ?? 0})

+ + {#each diffData.added ?? [] as p} +

{typeof p === 'string' ? p : (p as {prefix?: string}).prefix ?? JSON.stringify(p)}

+ {:else} +

Нет изменений

+ {/each} +
+
+
+

- Удалено ({diffData.removed?.length ?? 0})

+ + {#each diffData.removed ?? [] as p} +

{typeof p === 'string' ? p : (p as {prefix?: string}).prefix ?? JSON.stringify(p)}

+ {:else} +

Нет изменений

+ {/each} +
+
+
+ {/if} +
+
+
+ + + + + +
+ Задачи + Фоновые задачи (ingest, apply, refresh) +
+ +
+ + + + + Вид + Статус + Создана + Завершена + + + + + {#each jobs as job (job.job_id)} + openJobDetail(job)}> + {job.kind} + {job.status} + {formatDate(job.created_at)} + {formatDate(job.finished_at)} + + {#if job.status === 'running' || job.status === 'queued'} + + {/if} + + + {:else} + + + {jobsLoading ? 'Загрузка…' : 'Нет задач'} + + + {/each} + +
+
+
+
+
+
+ + + + + + Запустить Apply на всех спикерах? + Текущая конфигурация будет применена на всех BIRD-агентах. Требуется роль operator. + + + Отмена + {applying ? 'Apply…' : 'Применить'} + + + + + + + + + Reload BIRD? + BIRD перезагрузит конфигурацию. Требуется роль operator. + + + Отмена + {reloading ? 'Reload…' : 'Reload'} + + + + + + { if (!v) rollbackTarget = null; }}> + + + Откатиться к ревизии {rollbackTarget?.id.slice(0, 8)}…? + Будет создана новая ревизия на основе выбранной. Требуется роль operator. + + + (rollbackTarget = null)}>Отмена + {rollingBack ? 'Откат…' : 'Откатить'} + + + + + + { if (!v) cancelTarget = null; }}> + + + Отменить задачу? + Задача: {cancelTarget?.kind} ({cancelTarget?.job_id?.slice(0, 8)}…) + + + (cancelTarget = null)}>Нет + {cancelling ? 'Отмена…' : 'Отменить'} + + + + + + + + + Ревизия {previewRevision?.id.slice(0, 8)}… + Предпросмотр префиксов ревизии + + {#if previewLoading} +

Загрузка…

+ {:else} +

Префиксов: {prefixesData.length}

+ + {#each prefixesData as pfx} +

{pfx}

+ {:else} +

Нет префиксов

+ {/each} +
+ {/if} +
+
+ + + + + + Задача: {jobDetail?.kind} + + {#if jobDetail} +
+
+ ID{jobDetail.job_id} + Статус{jobDetail.status} + Создана{formatDate(jobDetail.created_at)} + Начата{formatDate(jobDetail.started_at)} + Завершена{formatDate(jobDetail.finished_at)} + {#if jobDetail.error} + Ошибка{jobDetail.error} + {/if} +
+ {#if jobDetail.meta && Object.keys(jobDetail.meta).length > 0} +
+

Meta

+
{JSON.stringify(jobDetail.meta, null, 2)}
+
+ {/if} +
+ {/if} +
+
diff --git a/web/src/routes/peers/+page.svelte b/web/src/routes/peers/+page.svelte index c35cae0..1d74be7 100644 --- a/web/src/routes/peers/+page.svelte +++ b/web/src/routes/peers/+page.svelte @@ -1,118 +1,5 @@ - -
-
-

Пиры и спикеры

-

BGP-пиры и экземпляры BIRD.

-
- - {#if err} -

{err}

- {/if} - - - - Спикеры - GET /v1/speakers - - - - - - - Роль - Endpoint - Последняя ревизия - - - - {#each speakers as s} - - {s.role} - {s.endpoint} - {s.last_applied_revision_id ?? '—'} - - {:else} - - Нет данных - - {/each} - -
-
-
-
- - - - Пиры - GET /v1/peers - - - - - - - Имя - Neighbor - Сессия - - - - {#each peers as p} - - {p.name} - {p.neighbor} - - {p.session_state} - - - {:else} - - Нет данных - - {/each} - -
-
-
-
-
diff --git a/web/src/routes/revisions/+page.svelte b/web/src/routes/revisions/+page.svelte index 8ca56ff..b7f92f1 100644 --- a/web/src/routes/revisions/+page.svelte +++ b/web/src/routes/revisions/+page.svelte @@ -1,80 +1,5 @@ - -
-
-

Ревизии

-

История конфигурации BIRD.

-
- - - - Ревизии - GET /v1/revisions - - - {#if err} -

{err}

- {:else} - - - - - ID - Хэш - Префиксы - Создана - - - - {#each rows as r} - - {r.id} - {r.content_hash} - {r.materialized_prefix_count ?? '—'} - {r.created_at} - - {:else} - - Нет данных - - {/each} - -
-
- {/if} -
-
-
diff --git a/web/src/routes/schedule/+page.svelte b/web/src/routes/schedule/+page.svelte index 45debd5..51dad7a 100644 --- a/web/src/routes/schedule/+page.svelte +++ b/web/src/routes/schedule/+page.svelte @@ -1,11 +1,10 @@
-
-

Расписание и задачи

-

- Интервалы обновления модулей (поля из API) и ручной refresh → ingest. Ниже — последние задачи из - job_audit. -

+
+
+

Расписание и задачи

+

Интервалы обновления модулей и ручной запуск refresh.

+
+
- Модули и refresh - - POST /v1/modules/{id}/refresh (роль editor+). CDN/домены/AS — очередь; IP_RANGES — 204. - + Модули + Запустить refresh вручную (CDN/домены/AS → очередь; IP_RANGES → 204) - - - - + +
+ + + Модуль + Тип + Интервал + Cron + + + + + {#each modules as m (m.id)} - Модуль - Тип - Интервал - Cron - Действие + {m.name} + {m.type} + {intervalLabel(m.refresh_interval_sec)} + {m.cron_expr || '—'} + + + - - - {#each modules as m} - - {m.name} - - {m.type} - - {intervalLabel(m.refresh_interval_sec)} - {m.cron_expr || '—'} - - - - - {:else} - - Нет модулей - - {/each} - -
-
+ {:else} + + + {loading ? 'Загрузка…' : 'Нет модулей'} + + + {/each} + +
@@ -129,33 +131,33 @@ Последние задачи GET /v1/jobs - - - - + +
+ + + Вид + Статус + Создана + Ошибка + + + + {#each jobs as j (j.job_id)} - Вид - Статус - Создана + {j.kind} + {j.status} + {j.created_at ? new Date(j.created_at).toLocaleString('ru') : '—'} + {j.error ?? ''} - - - {#each jobs as j} - - {j.kind} - - {j.status} - - {j.created_at ?? '—'} - - {:else} - - Нет задач - - {/each} - -
-
+ {:else} + + + {loading ? 'Загрузка…' : 'Нет задач'} + + + {/each} + +
diff --git a/web/src/routes/settings/+page.svelte b/web/src/routes/settings/+page.svelte index bc7159f..0858b0f 100644 --- a/web/src/routes/settings/+page.svelte +++ b/web/src/routes/settings/+page.svelte @@ -1,13 +1,22 @@ -
+

Настройки

-

- Bearer-токен для заголовка Authorization. Для - локального демо с - EVOBGP_DEV_INSECURE=1 - можно использовать токен dev. -

+

Токен доступа и параметры API.

+ API-ключ - Хранится только в localStorage + + Bearer-токен хранится только в localStorage браузера. Для локального демо с + EVOBGP_DEV_INSECURE=1 + используйте токен dev. +
- + +
+
+ + + + + Настройки системы (API) + + GET/PATCH /v1/settings — глобальные параметры control plane. + Требуется роль operator. + + + + {#if loadingSettings} +

Загрузка…

+ {:else if apiSettings !== null} +
+ +