Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1c7152a285 | ||
|
|
a37c931ee7 |
@@ -0,0 +1,34 @@
|
|||||||
|
# shadcn-svelte в EvoBGP monorepo
|
||||||
|
|
||||||
|
Использовать при добавлении примитивов, блоков, правке темы в `apps/web` / `packages/ui`.
|
||||||
|
|
||||||
|
## Структура
|
||||||
|
|
||||||
|
- Примитивы: `packages/ui/src/components/` → `@evobgp/ui/components/*`
|
||||||
|
- Паттерны приложения: `apps/web/src/lib/components/patterns/`
|
||||||
|
- CLI всегда из `apps/web/`
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
|
||||||
|
1. Проверить, есть ли компонент в `packages/ui/src/components/`
|
||||||
|
2. `cd apps/web && pnpm dlx shadcn-svelte@latest add <name> -y -o`
|
||||||
|
3. Если файлы в `apps/web/@evobgp/ui/` — перенести в `packages/ui/src/components/`
|
||||||
|
4. Импорты shadcn в app: `@evobgp/ui/components/<name>/index.js`
|
||||||
|
5. `pnpm --filter @evobgp/web check && pnpm --filter @evobgp/web lint`
|
||||||
|
|
||||||
|
## Тема
|
||||||
|
|
||||||
|
- `packages/ui/src/styles/globals.css` с `@source`
|
||||||
|
- Импорт в `apps/web/src/routes/layout.css`: `@import '@evobgp/ui/styles/globals.css';`
|
||||||
|
|
||||||
|
## Не использовать
|
||||||
|
|
||||||
|
- React shadcn/ui примеры без адаптации под Svelte 5
|
||||||
|
- ReUI (`@reui/*`) — только React
|
||||||
|
- Legacy `$lib/components/ui/` re-exports
|
||||||
|
|
||||||
|
## Документация
|
||||||
|
|
||||||
|
- https://shadcn-svelte.com/docs
|
||||||
|
- https://shadcn-svelte.com/llms.txt
|
||||||
|
- Monorepo: `.cursor/rules/frontend-monorepo.mdc`
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"pid": 44608,
|
"pid": 46304,
|
||||||
"version": "0.9.9",
|
"version": "0.9.9",
|
||||||
"socketPath": "\\\\.\\pipe\\codegraph-97b92efdcc5351da",
|
"socketPath": "\\\\.\\pipe\\codegraph-97b92efdcc5351da",
|
||||||
"startedAt": 1781240018712
|
"startedAt": 1782824332519
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
---
|
||||||
|
description: shadcn-svelte Monorepo — apps/web + packages/ui + packages/shared, CLI workflow
|
||||||
|
globs: apps/web/**/*,packages/ui/**/*,packages/shared/**/*
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
|
||||||
|
# Frontend Monorepo (shadcn-svelte)
|
||||||
|
|
||||||
|
Структура по образцу vps-tracker, стек — **SvelteKit 2 + Svelte 5 + shadcn-svelte** (не React/ReUI).
|
||||||
|
|
||||||
|
## Layout
|
||||||
|
|
||||||
|
```
|
||||||
|
apps/web/ # SvelteKit (routes, queries, domain components)
|
||||||
|
packages/ui/ # @evobgp/ui — shadcn-svelte primitives (только CLI output)
|
||||||
|
packages/shared/ # @evobgp/shared — Zod contracts, API types
|
||||||
|
```
|
||||||
|
|
||||||
|
Go API в корне репозитория (`internal/`, `cmd/`) без изменений.
|
||||||
|
|
||||||
|
## Два components.json
|
||||||
|
|
||||||
|
| Файл | Назначение |
|
||||||
|
|------|------------|
|
||||||
|
| `apps/web/components.json` | App aliases; `ui` → `@evobgp/ui/components` |
|
||||||
|
| `packages/ui/components.json` | UI package aliases |
|
||||||
|
|
||||||
|
**Синхронизировать:** `style`, `iconLibrary`, `baseColor` в обоих файлах.
|
||||||
|
|
||||||
|
## CLI — только из apps/web
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
cd apps/web
|
||||||
|
pnpm dlx shadcn-svelte@latest add button -y -o
|
||||||
|
pnpm dlx shadcn-svelte@latest add sidebar -y -o
|
||||||
|
pnpm dlx shadcn-svelte@latest add breadcrumb -y -o
|
||||||
|
```
|
||||||
|
|
||||||
|
Перед обновлением существующих компонентов: `pnpm dlx shadcn-svelte@latest add button --dry-run`
|
||||||
|
|
||||||
|
## Куда CLI кладёт файлы
|
||||||
|
|
||||||
|
| Команда | Куда |
|
||||||
|
|---------|------|
|
||||||
|
| `add button` | `packages/ui/src/components/button/` |
|
||||||
|
| `add sidebar` | `packages/ui/src/components/sidebar/` |
|
||||||
|
|
||||||
|
Если CLI кладёт в `apps/web/@evobgp/ui/` — перенести в `packages/ui/src/components/`.
|
||||||
|
|
||||||
|
## Импорты
|
||||||
|
|
||||||
|
```svelte
|
||||||
|
import { Button } from '@evobgp/ui/components/button/index.js';
|
||||||
|
import { cn } from '@evobgp/ui/lib/utils';
|
||||||
|
import '@evobgp/ui/styles/globals.css'; // только в routes/layout.css
|
||||||
|
import type { ModuleRow } from '@evobgp/shared/types/api.js';
|
||||||
|
import { moduleCreateSchema } from '@evobgp/shared/contracts/modules.js';
|
||||||
|
```
|
||||||
|
|
||||||
|
| Запрещено | Разрешено |
|
||||||
|
|-----------|-----------|
|
||||||
|
| `$lib/ui/core/*` | `@evobgp/ui/components/*` |
|
||||||
|
| `apps/web/src/lib/components/ui/` (legacy re-export) | `packages/ui/src/components/` |
|
||||||
|
| Ручное редактирование темы вне CLI | shadcn-svelte theming docs |
|
||||||
|
|
||||||
|
## globals.css
|
||||||
|
|
||||||
|
`packages/ui/src/styles/globals.css` с `@source` на `packages/ui` и `apps/web/src`.
|
||||||
|
|
||||||
|
## Проверка после правок
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
pnpm --filter @evobgp/web check
|
||||||
|
pnpm --filter @evobgp/web lint
|
||||||
|
```
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
---
|
||||||
|
description: UI-паттерны apps/web — shared components, spacing, матрица (Svelte, без ReUI)
|
||||||
|
globs: apps/web/**/*
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
|
||||||
|
# Frontend UI Patterns (Svelte)
|
||||||
|
|
||||||
|
Эталон UX: vps-tracker (`PageShell`, `QueryState`, `SectionCards`, `DataTableCard`). ReUI **не используется** — Data Table = shadcn-svelte + `AppDataTable` / `DataTableCard`.
|
||||||
|
|
||||||
|
## Иерархия
|
||||||
|
|
||||||
|
```
|
||||||
|
@evobgp/ui/components/* ← shadcn-svelte CLI (packages/ui)
|
||||||
|
apps/web/src/lib/components/ ← shared + domain + layout
|
||||||
|
page-shell.svelte
|
||||||
|
query-state.svelte
|
||||||
|
section-cards.svelte
|
||||||
|
data-table-card.svelte
|
||||||
|
status-badge.svelte
|
||||||
|
form-sheet.svelte
|
||||||
|
crud-list-page.svelte
|
||||||
|
list-filters-bar.svelte
|
||||||
|
patterns/ ← AppDataTable, FormField, ConfirmDialog, EmptyState
|
||||||
|
layout/app-shell.svelte
|
||||||
|
domain-*/ ← modules, network, operations…
|
||||||
|
```
|
||||||
|
|
||||||
|
## Матрица
|
||||||
|
|
||||||
|
| Элемент | Компонент | Primitive |
|
||||||
|
|---------|-----------|-----------|
|
||||||
|
| Page wrapper | `PageShell` | — |
|
||||||
|
| Page title | `PageHeader` (`ui/app/page-header`) | — |
|
||||||
|
| Stat metrics | `SectionCards` | `Card` |
|
||||||
|
| Data list | `DataTableCard` + `AppDataTable` | `Table` |
|
||||||
|
| Filters | `ListFiltersBar` | `Select`, `Badge` |
|
||||||
|
| Empty | `EmptyState` | — |
|
||||||
|
| Loading / Error | `QueryState` | `Skeleton`, `Alert` |
|
||||||
|
| Status | `StatusBadge` | `Badge` |
|
||||||
|
| Create/Edit form | `FormSheet` / Dialog | `Sheet`, `Field` |
|
||||||
|
| Delete confirm | `ConfirmDialog` | `AlertDialog` |
|
||||||
|
| Nav | `AppShell` | `Sidebar` |
|
||||||
|
| Breadcrumbs | `AppShell` header | `Breadcrumb` |
|
||||||
|
|
||||||
|
## Spacing
|
||||||
|
|
||||||
|
- `flex` + `gap-*`, не `space-y-*`
|
||||||
|
- Page: `gap-4 md:gap-6`, padding `p-4 md:p-6`
|
||||||
|
- Loading страницы → `Skeleton`, не page-level `Spinner`
|
||||||
|
- Max 1 primary CTA на экран
|
||||||
|
|
||||||
|
## Docs
|
||||||
|
|
||||||
|
- shadcn-svelte: https://shadcn-svelte.com/docs
|
||||||
|
- llms.txt: https://shadcn-svelte.com/llms.txt
|
||||||
|
- Data layer: `@tanstack/svelte-query` в `apps/web/src/lib/queries/`
|
||||||
|
|
||||||
|
## Overlay
|
||||||
|
|
||||||
|
| Сценарий | Компонент |
|
||||||
|
|----------|-----------|
|
||||||
|
| Create/edit | `Sheet` / `FormSheet` |
|
||||||
|
| Destructive | `ConfirmDialog` |
|
||||||
|
| Preview | `Dialog` |
|
||||||
+33
-104
@@ -1,126 +1,55 @@
|
|||||||
---
|
---
|
||||||
description: EvoBGP WebUI — shadcn-svelte, Svelte 5, слои ui/core|patterns|app
|
description: EvoBGP WebUI — shadcn-svelte monorepo, Svelte 5, @evobgp/ui
|
||||||
globs:
|
globs:
|
||||||
- web/**
|
- apps/web/**
|
||||||
|
- packages/ui/**
|
||||||
|
- packages/shared/**
|
||||||
alwaysApply: false
|
alwaysApply: false
|
||||||
---
|
---
|
||||||
|
|
||||||
# Web UI — shadcn-svelte
|
# Web UI — shadcn-svelte (monorepo)
|
||||||
|
|
||||||
**Источник правды:** https://shadcn-svelte.com/docs (не React shadcn/ui, не Legacy Docs).
|
**Источник правды:** https://shadcn-svelte.com/docs
|
||||||
|
|
||||||
Общие правила Go/API: `.cursor/rules/engineering.mdc`. Локальная карта: `web/README.md`.
|
Monorepo: [`frontend-monorepo.mdc`](frontend-monorepo.mdc), паттерны: [`frontend-ui-patterns.mdc`](frontend-ui-patterns.mdc). Skill: [`.agents/skills/shadcn-svelte/SKILL.md`](.agents/skills/shadcn-svelte/SKILL.md).
|
||||||
|
|
||||||
## Слои UI
|
## Слои
|
||||||
|
|
||||||
| Слой | Путь | Назначение |
|
| Слой | Путь | Назначение |
|
||||||
|------|------|------------|
|
|------|------|------------|
|
||||||
| Примитивы | `src/lib/ui/core/` | shadcn-svelte (только CLI `add`) |
|
| Примитивы | `packages/ui/src/components/` | shadcn-svelte (только CLI) |
|
||||||
| Паттерны | `src/lib/ui/patterns/` | FormField, AppDataTable, ConfirmDialog, EmptyState |
|
| Паттерны | `apps/web/src/lib/components/patterns/` | AppDataTable, FormField, ConfirmDialog |
|
||||||
| App chrome | `src/lib/ui/app/` | Layout, PageHeader, `notify` |
|
| Shared UI | `apps/web/src/lib/components/` | PageShell, QueryState, SectionCards |
|
||||||
| Legacy | `src/lib/components/ui/` | Re-export; **не добавлять новые файлы** |
|
| App chrome | `apps/web/src/lib/ui/app/` | PageHeader, toast, nav |
|
||||||
|
| Контракты | `packages/shared/` | types + Zod |
|
||||||
|
|
||||||
Тема: `src/routes/layout.css`, `src/lib/ui/app/tokens.md`. CLI из `web/`: `npx shadcn-svelte@latest add <component> -y -o`.
|
Тема: `packages/ui/src/styles/globals.css` → импорт в `apps/web/src/routes/layout.css`.
|
||||||
|
|
||||||
---
|
CLI из `apps/web/`:
|
||||||
|
|
||||||
## Правила
|
|
||||||
|
|
||||||
**WEB-01** | MUST | Перед новым UI — проверить https://shadcn-svelte.com/docs/components; использовать компонент, не HTML+CSS с нуля.
|
|
||||||
*Rationale:* Open Code + единый дизайн.
|
|
||||||
*Проверка:* review; нет голых `<button class=…>`.
|
|
||||||
|
|
||||||
**WEB-02** | MUST | Отсутствующий примитив — `npx shadcn-svelte@latest add <component> -y -o` → `src/lib/ui/core/`.
|
|
||||||
*Rationale:* Distribution через CLI и `components.json`.
|
|
||||||
*Проверка:* файлы только в `ui/core`.
|
|
||||||
|
|
||||||
**WEB-03** | NEVER | Альтернативные UI-kit'ы (Material, Vuetify, DaisyUI-only без shadcn-примитива).
|
|
||||||
*Проверка:* `package.json` review.
|
|
||||||
|
|
||||||
**WEB-04** | MUST | Комозиция по docs: все sub-компоненты (`DialogHeader`, `TableRow`, `Field`, …).
|
|
||||||
*Проверка:* сверка со страницей компонента в docs.
|
|
||||||
|
|
||||||
**WEB-05** | MUST | Формы — Formsnap + `sveltekit-superforms`; UI в `ui/patterns/form`, не ad-hoc валидация на странице.
|
|
||||||
*Проверка:* https://shadcn-svelte.com/docs/components/form
|
|
||||||
|
|
||||||
**WEB-06** | MUST | Таблицы — Data Table + `@tanstack/table-core`; на страницах — `AppDataTable` из patterns.
|
|
||||||
*Проверка:* https://shadcn-svelte.com/docs/components/data-table
|
|
||||||
|
|
||||||
**WEB-07** | MUST | Toast — Sonner через `notify` из `ui/app/toast.js`.
|
|
||||||
*Проверка:* https://shadcn-svelte.com/docs/components/sonner
|
|
||||||
|
|
||||||
**WEB-08** | MUST | Иконки — `@lucide/svelte` (`components.json` → `iconLibrary: lucide`).
|
|
||||||
*Проверка:* imports.
|
|
||||||
|
|
||||||
**WEB-09** | MUST | Цвета — CSS-переменные `layout.css` и токены `tokens.md`; не hex/rgb на страницах.
|
|
||||||
*Проверка:* grep `#[0-9a-f]{3,6}` в `routes/`.
|
|
||||||
|
|
||||||
**WEB-10** | SHOULD | Кастомизация — правка `ui/core` (Open Code), не `!important` поверх API.
|
|
||||||
*Проверка:* review.
|
|
||||||
|
|
||||||
**WEB-11** | MUST | `routes/**` — композиция `ui/core` + `ui/patterns` + `ui/app`; не копировать целые примитивы shadcn в route.
|
|
||||||
*Проверка:* review.
|
|
||||||
|
|
||||||
**WEB-12** | NEVER | Примеры React shadcn/ui или Svelte 4 Legacy без адаптации под https://shadcn-svelte.com/docs/migration/svelte-5
|
|
||||||
*Проверка:* `npm run check`.
|
|
||||||
|
|
||||||
**WEB-13** | MUST | Реактивность — Svelte 5 runes (`$state`, `$derived`, `$effect`); не `export let` для локального state страниц.
|
|
||||||
*Проверка:* `npm run check`; Svelte MCP.
|
|
||||||
|
|
||||||
**WEB-14** | SHOULD | Нетривиальный UI — прочитать страницу компонента (props, a11y).
|
|
||||||
*Проверка:* PR description.
|
|
||||||
|
|
||||||
**WEB-15** | MUST | Сомнения — https://shadcn-svelte.com/llms.txt , Svelte MCP, `npm run check`.
|
|
||||||
*Проверка:* локально.
|
|
||||||
|
|
||||||
**WEB-19** | MUST | **После любого изменения `web/**`** — перед завершением задачи агент **обязан** выполнить в `web/`:
|
|
||||||
```powershell
|
```powershell
|
||||||
npm run check
|
pnpm dlx shadcn-svelte@latest add <component> -y -o
|
||||||
npm run lint
|
|
||||||
```
|
```
|
||||||
Если `npm run lint` падает (Prettier) — **сначала** `npx prettier --write <изменённые файлы>` или `npx prettier --write .`, затем снова `npm run check` и `npm run lint`. Не сдавать PR/ответ, пока обе команды не exit 0.
|
|
||||||
*Rationale:* CI job `web` = `check` + `prettier --check`; `svelte-check` не ловит форматирование.
|
|
||||||
*Проверка:* CI job `web`; pre-commit hook `prettier-web`.
|
|
||||||
|
|
||||||
**WEB-16** | MUST | Подтверждение удаления — `ConfirmDialog` из patterns, не `window.confirm`.
|
## Правила (кратко)
|
||||||
*Проверка:* review.
|
|
||||||
|
|
||||||
**WEB-17** | MUST | Пустые списки — `EmptyState` из patterns.
|
- **WEB-01** Новый UI — сначала shadcn-svelte docs; не HTML+CSS с нуля
|
||||||
*Проверка:* review.
|
- **WEB-02** Примитивы только через CLI → `packages/ui`
|
||||||
|
- **WEB-05** Формы — Formsnap + superforms; `patterns/form/`
|
||||||
|
- **WEB-06** Таблицы — `AppDataTable` / `DataTableCard`
|
||||||
|
- **WEB-07** Toast — `notify` из `ui/app/toast.ts`
|
||||||
|
- **WEB-08** Иконки — `@lucide/svelte`
|
||||||
|
- **WEB-09** Цвета — CSS variables; не hex в routes
|
||||||
|
- **WEB-13** Svelte 5 runes
|
||||||
|
- **WEB-16** Удаление — `ConfirmDialog`
|
||||||
|
- **WEB-17** Пустые списки — `EmptyState`
|
||||||
|
- **WEB-19** После правок `apps/web/**`:
|
||||||
|
|
||||||
**WEB-18** | SHOULD | Повторяемая комбинация core (≥2 раза) — вынести в `ui/patterns/`.
|
```powershell
|
||||||
*Проверка:* review.
|
pnpm --filter @evobgp/web check
|
||||||
|
pnpm --filter @evobgp/web lint
|
||||||
---
|
```
|
||||||
|
|
||||||
## Documentation Sync (Web)
|
|
||||||
|
|
||||||
**DOC-SYNC-06** | MUST | UI — первично https://shadcn-svelte.com/docs; при конфликте с блогами/Stack Overflow побеждает официальная страница компонента.
|
|
||||||
**DOC-SYNC-07** | MUST | Перед `add` — сверить Installation/Theming с `web/components.json` и `src/routes/layout.css`.
|
|
||||||
|
|
||||||
Tailwind v4: https://shadcn-svelte.com/docs/migration/tailwind-v4
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Enforcement
|
## Enforcement
|
||||||
|
|
||||||
**Обязательный финальный шаг агента при правках `web/**`:** `npm run check` **и** `npm run lint` (см. **WEB-19**). Только `check` недостаточно.
|
PR checklist: `check` + `lint` exit 0; примитивы в `@evobgp/ui`, не дубли в routes.
|
||||||
|
|
||||||
```powershell
|
|
||||||
cd web
|
|
||||||
npm run check
|
|
||||||
npm run lint
|
|
||||||
# при warn/fail lint:
|
|
||||||
npx prettier --write .
|
|
||||||
npm run check
|
|
||||||
npm run lint
|
|
||||||
```
|
|
||||||
|
|
||||||
**PR checklist `web/**`:**
|
|
||||||
- [ ] `npm run check` — exit 0
|
|
||||||
- [ ] `npm run lint` (prettier --check) — exit 0
|
|
||||||
- [ ] `ui/core` / `ui/patterns`, не дубли примитивов
|
|
||||||
- [ ] Новые примитивы через shadcn CLI
|
|
||||||
- [ ] Ссылка на docs компонента (если новый паттерн)
|
|
||||||
|
|
||||||
**CI:** job `web` — `npm run check` + `npm run lint`.
|
|
||||||
|
|||||||
+11
-11
@@ -98,9 +98,9 @@ jobs:
|
|||||||
openapi=true
|
openapi=true
|
||||||
go=true
|
go=true
|
||||||
;;
|
;;
|
||||||
web/README.md|web/components.json)
|
apps/web/README.md|apps/web/components.json)
|
||||||
;;
|
;;
|
||||||
web/*)
|
apps/web/*|packages/ui/*|packages/shared/*|pnpm-workspace.yaml|pnpm-lock.yaml)
|
||||||
web=true
|
web=true
|
||||||
;;
|
;;
|
||||||
deploy/bird/*)
|
deploy/bird/*)
|
||||||
@@ -126,7 +126,7 @@ jobs:
|
|||||||
docs/*)
|
docs/*)
|
||||||
go=true
|
go=true
|
||||||
;;
|
;;
|
||||||
package.json|package-lock.json|.releaserc.json)
|
package.json|package-lock.json|pnpm-lock.yaml|pnpm-workspace.yaml|.releaserc.json)
|
||||||
full_pipeline=true
|
full_pipeline=true
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
@@ -169,16 +169,16 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: "20"
|
node-version: "22"
|
||||||
cache: npm
|
- uses: pnpm/action-setup@v4
|
||||||
cache-dependency-path: web/package-lock.json
|
with:
|
||||||
- name: npm ci, check, lint
|
version: 10.33.2
|
||||||
|
- name: pnpm install, check, lint
|
||||||
run: |
|
run: |
|
||||||
set -euxo pipefail
|
set -euxo pipefail
|
||||||
cd web
|
pnpm install --frozen-lockfile
|
||||||
npm ci
|
pnpm --filter @evobgp/web check
|
||||||
npm run check
|
pnpm --filter @evobgp/web lint
|
||||||
npm run lint
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
go:
|
go:
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ repos:
|
|||||||
hooks:
|
hooks:
|
||||||
- id: prettier-web
|
- id: prettier-web
|
||||||
name: prettier (web)
|
name: prettier (web)
|
||||||
entry: bash -c 'cd web && npx prettier --check .'
|
entry: bash -c 'pnpm --filter @evobgp/web lint'
|
||||||
language: system
|
language: system
|
||||||
files: ^web/
|
files: ^apps/web/
|
||||||
pass_filenames: false
|
pass_filenames: false
|
||||||
|
|||||||
@@ -1,70 +1,48 @@
|
|||||||
# Руководство для ИИ-агентов (экономия контекста)
|
# EvoBGP — руководство для ИИ-агентов
|
||||||
|
|
||||||
Краткие ориентиры по репозиторию **EvoBGP**, чтобы не тратить токены на полное сканирование дерева и повторное чтение одних и тех же файлов.
|
Краткая карта репозитория. Полная архитектура: [docs/architecture.md](docs/architecture.md). HTTP: [docs/openapi.yaml](docs/openapi.yaml).
|
||||||
|
|
||||||
## С чего начать (минимум чтения)
|
## Monorepo (фронтенд)
|
||||||
|
|
||||||
0. **Инженерные правила** — при изменении кода следовать [.cursor/rules/engineering.mdc](.cursor/rules/engineering.mdc); для `web/` — [.cursor/rules/web-shadcn.mdc](.cursor/rules/web-shadcn.mdc); для `birdfmt` / `pipeline` / BIRD — [.cursor/rules/networking-bird.mdc](.cursor/rules/networking-bird.mdc). **Context7 (документация библиотек)** — закреплённые ID стека: [.cursor/rules/context7-stack.mdc](.cursor/rules/context7-stack.mdc); скилл [.cursor/skills/context7-evobgp/SKILL.md](.cursor/skills/context7-evobgp/SKILL.md).
|
```
|
||||||
1. **[docs/README.md](docs/README.md)** — оглавление и роли читателя.
|
EvoBGP/
|
||||||
2. **[docs/architecture.md](docs/architecture.md)** — компоненты `cmd/`, карта `internal/`, потоки данных (одного этого файла обычно достаточно для ориентации).
|
├── cmd/, internal/ # Go API /v1
|
||||||
3. Задача-специфично: [docs/api.md](docs/api.md), [docs/access.md](docs/access.md), [web/README.md](web/README.md) — только если меняете API, доступ или фронт.
|
├── apps/web/ # SvelteKit SPA (@evobgp/web)
|
||||||
|
├── packages/ui/ # @evobgp/ui — shadcn-svelte primitives
|
||||||
Источник правды по HTTP-контракту: **[docs/openapi.yaml](docs/openapi.yaml)**. Не дублируйте длинные фрагменты спецификации в ответах — ссылайтесь на путь и тег/операцию.
|
├── packages/shared/ # @evobgp/shared — types, Zod contracts
|
||||||
|
└── pnpm-workspace.yaml
|
||||||
## Карта кода (куда смотреть)
|
|
||||||
|
|
||||||
| Область | Где искать |
|
|
||||||
|---------|------------|
|
|
||||||
| REST, auth, CORS | `internal/httpapi/` |
|
|
||||||
| Бизнес-слой и абстракция хранилища | `internal/store/` |
|
|
||||||
| PostgreSQL | `internal/repository/`, `internal/db/`, `migrations/` |
|
|
||||||
| Фоновые задачи | `internal/jobs/` |
|
|
||||||
| Цепочка refresh модуля (ingest+render, BIRD preview) | `internal/pipeline/` |
|
|
||||||
| Конфиг BIRD, `birdc` | `internal/birdfmt/`, `internal/birddeploy/` |
|
|
||||||
| Бандлы и подписи | `internal/bundle/`, `internal/signing/` |
|
|
||||||
| Точки входа процессов | `cmd/*/` |
|
|
||||||
| Веб (SvelteKit) | `web/` |
|
|
||||||
| Compose, деплой | `deploy/compose/` |
|
|
||||||
|
|
||||||
Точки входа бинарников и их роли — в таблице в начале [docs/architecture.md](docs/architecture.md).
|
|
||||||
|
|
||||||
## Как не раздувать контекст
|
|
||||||
|
|
||||||
- **Сначала узкий поиск:** `grep`/поиск по символу или короткий семантический запрос по одной папке (`internal/httpapi/`, `internal/pipeline/`, …), а не чтение всех `.go` подряд.
|
|
||||||
- **Читайте файлы целиком только при необходимости:** большие файлы — с `offset`/`limit` или по найденным строкам.
|
|
||||||
- **Не подтягивайте в контекст:** `web/node_modules/`, сгенерированные артефакты сборки, бинарники, полный `openapi.html`, если достаточно `openapi.yaml`.
|
|
||||||
- **Повторное использование:** если [docs/architecture.md](docs/architecture.md) уже описывает поток — не пересказывайте его длинно; укажите документ и конкретный подпункт задачи.
|
|
||||||
- **Длинные планы:** `.cursor/plans/*.plan.md` — для истории решений; для навигации пользователю достаточно `docs/`; не читайте план целиком без причины.
|
|
||||||
|
|
||||||
## Коммиты (Conventional Commits)
|
|
||||||
|
|
||||||
Если пользователь просит **коммит**, **commit message**, **закоммить**, **git commit**, **`/commit-message`** или это следует из плана — **сразу**:
|
|
||||||
|
|
||||||
1. Shell: `powershell -NoProfile -File scripts/commit/staged-context.ps1` (первый вызов, до текста коммита).
|
|
||||||
2. Скилл [.cursor/skills/commit-message/SKILL.md](.cursor/skills/commit-message/SKILL.md) и правило [.cursor/rules/conventional-commits.mdc](.cursor/rules/conventional-commits.mdc).
|
|
||||||
|
|
||||||
Без вывода скрипта (exit 0) **не** придумывать сообщение коммита. Заголовок — EN, тело — RU; несвязанные области — auto-split (скилл).
|
|
||||||
|
|
||||||
**Кнопка ✨ Generate commit message в Source Control** skill/rule **не** использует. Для сообщений по правилам EvoBGP — Agent Chat → **`/commit-message`** (см. [.cursor/commands/commit-message.md](.cursor/commands/commit-message.md)).
|
|
||||||
|
|
||||||
## Команды и среда
|
|
||||||
|
|
||||||
- Консоль пользователя: **PowerShell**; пути в стиле `deploy\compose`.
|
|
||||||
- Быстрый старт и переменные: [docs/quickstart.md](docs/quickstart.md), [README.md](README.md).
|
|
||||||
- **Go:** после правок — `gofmt -w`, `go vet ./...`, `scripts/lint-go.ps1` (как CI golangci-lint).
|
|
||||||
|
|
||||||
## Язык документации проекта
|
|
||||||
|
|
||||||
Пользовательская документация в `docs/` — преимущественно на русском. Комментарии и имена в коде — в существующем стиле репозитория.
|
|
||||||
|
|
||||||
## Svelte / фронтенд
|
|
||||||
|
|
||||||
При правках `web/**/*.svelte` или Svelte-модулей следуйте [.cursor/rules/web-shadcn.mdc](.cursor/rules/web-shadcn.mdc) (**WEB-19**): перед завершением задачи **обязательно**:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
cd web
|
|
||||||
npm run check
|
|
||||||
npm run lint
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Если `lint` падает — `npx prettier --write .` и повторить обе команды. CI job `web` не пропускает без этого.
|
| Слой | Импорт |
|
||||||
|
|------|--------|
|
||||||
|
| Примитивы | `@evobgp/ui/components/*` |
|
||||||
|
| CSS | `@evobgp/ui/styles/globals.css` |
|
||||||
|
| Типы/контракты | `@evobgp/shared/types/*`, `@evobgp/shared/contracts/*` |
|
||||||
|
| Паттерны UI | `$lib/components/*`, `$lib/components/patterns/*` |
|
||||||
|
| Queries | `$lib/queries/*` (`@tanstack/svelte-query`) |
|
||||||
|
|
||||||
|
## С чего начать
|
||||||
|
|
||||||
|
1. **Правила:** [engineering.mdc](.cursor/rules/engineering.mdc), [web-shadcn.mdc](.cursor/rules/web-shadcn.mdc), [frontend-monorepo.mdc](.cursor/rules/frontend-monorepo.mdc)
|
||||||
|
2. **Фронт:** [apps/web/README.md](apps/web/README.md)
|
||||||
|
3. **Go:** `internal/httpapi/`, `internal/store/`, `internal/pipeline/`
|
||||||
|
|
||||||
|
## Команды
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
pnpm install
|
||||||
|
pnpm --filter @evobgp/web dev
|
||||||
|
pnpm --filter @evobgp/web build
|
||||||
|
pnpm --filter @evobgp/web check
|
||||||
|
pnpm --filter @evobgp/web lint
|
||||||
|
```
|
||||||
|
|
||||||
|
После правок `apps/web/**` — **обязательно** `check` и `lint` (WEB-19).
|
||||||
|
|
||||||
|
## UX-эталон
|
||||||
|
|
||||||
|
vps-tracker: `PageShell`, `SectionCards`, `QueryState`, `DataTableCard`, sidebar-07 layout. ReUI не используется — Data Table на shadcn-svelte.
|
||||||
|
|
||||||
|
## Коммиты
|
||||||
|
|
||||||
|
При запросе коммита: [conventional-commits.mdc](.cursor/rules/conventional-commits.mdc), скилл `.cursor/skills/commit-message/SKILL.md`.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# EvoBGP
|
# EvoBGP
|
||||||
|
|
||||||
Control plane для управления префиксами, модулями ingest, ревизиями конфигурации BIRD и выкладкой на BGP-спикеры. Репозиторий включает HTTP API на Go, веб-интерфейс (`web/`), CLI для реплик (`evobgp-node`), агент и Docker Compose для локального и эталонного развёртывания.
|
Control plane для управления префиксами, модулями ingest, ревизиями конфигурации BIRD и выкладкой на BGP-спикеры. Репозиторий включает HTTP API на Go, веб-интерфейс (`apps/web/`), CLI для реплик (`evobgp-node`), агент и Docker Compose для локального и эталонного развёртывания.
|
||||||
|
|
||||||
## Документация
|
## Документация
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
# EvoBGP WebUI (`@evobgp/web`)
|
||||||
|
|
||||||
|
SvelteKit-панель управления EvoBGP. Monorepo: `apps/web` + `packages/ui` + `packages/shared`.
|
||||||
|
|
||||||
|
Правила: [.cursor/rules/web-shadcn.mdc](../../.cursor/rules/web-shadcn.mdc), [frontend-monorepo.mdc](../../.cursor/rules/frontend-monorepo.mdc).
|
||||||
|
|
||||||
|
## Структура
|
||||||
|
|
||||||
|
| Путь | Назначение |
|
||||||
|
| ------------------------------ | ------------------------------------------- |
|
||||||
|
| `packages/ui/src/components/` | shadcn-svelte примитивы (`@evobgp/ui`) |
|
||||||
|
| `src/lib/components/` | PageShell, QueryState, SectionCards, domain |
|
||||||
|
| `src/lib/components/patterns/` | AppDataTable, FormField, ConfirmDialog |
|
||||||
|
| `src/lib/ui/app/` | PageHeader, nav, toast |
|
||||||
|
| `src/lib/queries/` | TanStack Query factories |
|
||||||
|
| `packages/shared/` | API types + Zod contracts |
|
||||||
|
|
||||||
|
## Разработка
|
||||||
|
|
||||||
|
Из корня репозитория:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
pnpm install
|
||||||
|
pnpm --filter @evobgp/web dev
|
||||||
|
pnpm --filter @evobgp/web check
|
||||||
|
pnpm --filter @evobgp/web lint
|
||||||
|
pnpm --filter @evobgp/web build
|
||||||
|
```
|
||||||
|
|
||||||
|
Добавление shadcn-svelte (из `apps/web/`):
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
pnpm dlx shadcn-svelte@latest add <component> -y -o
|
||||||
|
```
|
||||||
|
|
||||||
|
Тема: `packages/ui/src/styles/globals.css` → импорт в `src/routes/layout.css`.
|
||||||
|
|
||||||
|
## Проверка перед PR
|
||||||
|
|
||||||
|
`pnpm --filter @evobgp/web check` и `lint` — exit 0 (WEB-19).
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://www.shadcn-svelte.com/schema.json",
|
"$schema": "https://www.shadcn-svelte.com/schema.json",
|
||||||
"tailwind": {
|
"tailwind": {
|
||||||
"css": "src/routes/layout.css",
|
"css": "../../packages/ui/src/styles/globals.css",
|
||||||
"baseColor": "neutral"
|
"baseColor": "neutral"
|
||||||
},
|
},
|
||||||
"aliases": {
|
"aliases": {
|
||||||
"components": "$lib/components",
|
"components": "$lib/components",
|
||||||
"utils": "$lib/utils",
|
"utils": "$lib/utils",
|
||||||
"ui": "$lib/ui/core",
|
"ui": "@evobgp/ui/components",
|
||||||
"hooks": "$lib/hooks",
|
"hooks": "$lib/hooks",
|
||||||
"lib": "$lib"
|
"lib": "$lib"
|
||||||
},
|
},
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "web",
|
"name": "@evobgp/web",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -33,6 +33,9 @@
|
|||||||
"vite": "^7.3.1"
|
"vite": "^7.3.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@evobgp/shared": "workspace:*",
|
||||||
|
"@evobgp/ui": "workspace:*",
|
||||||
|
"@tanstack/svelte-query": "^5.90.2",
|
||||||
"@tanstack/table-core": "^8.21.3",
|
"@tanstack/table-core": "^8.21.3",
|
||||||
"bits-ui": "^2.17.2",
|
"bits-ui": "^2.17.2",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from '@evobgp/shared/types/api.js';
|
||||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
+13
-8
@@ -1,14 +1,14 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { apiMutate } from '$lib/api/client.js';
|
import { apiMutate } from '$lib/api/client.js';
|
||||||
import type { ApiKey, ApiKeyCreate, ApiKeyCreated, ApiKeyRole } from '$lib/api/types.js';
|
import type { ApiKey, ApiKeyCreate, ApiKeyCreated, ApiKeyRole } from '$lib/api/types.js';
|
||||||
import { Button } from '$lib/ui/core/button/index.js';
|
import { Button } from '@evobgp/ui/components/button/index.js';
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
CardDescription,
|
CardDescription,
|
||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle
|
CardTitle
|
||||||
} from '$lib/ui/core/card/index.js';
|
} from '@evobgp/ui/components/card/index.js';
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@@ -16,12 +16,17 @@
|
|||||||
DialogFooter,
|
DialogFooter,
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle
|
DialogTitle
|
||||||
} from '$lib/ui/core/dialog/index.js';
|
} from '@evobgp/ui/components/dialog/index.js';
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger } from '$lib/ui/core/select/index.js';
|
import {
|
||||||
import FormField from '$lib/ui/patterns/form/form-field.svelte';
|
Select,
|
||||||
import AppInput from '$lib/ui/patterns/form/app-input.svelte';
|
SelectContent,
|
||||||
import AppDataTable from '$lib/ui/patterns/data-table/app-data-table.svelte';
|
SelectItem,
|
||||||
import { confirm } from '$lib/ui/patterns/confirm/confirm-state.svelte.js';
|
SelectTrigger
|
||||||
|
} from '@evobgp/ui/components/select/index.js';
|
||||||
|
import FormField from '$lib/components/patterns/form/form-field.svelte';
|
||||||
|
import AppInput from '$lib/components/patterns/form/app-input.svelte';
|
||||||
|
import AppDataTable from '$lib/components/patterns/data-table/app-data-table.svelte';
|
||||||
|
import { confirm } from '$lib/components/patterns/confirm/confirm-state.svelte.js';
|
||||||
import { notify, notifyApiError } from '$lib/ui/app/toast.js';
|
import { notify, notifyApiError } from '$lib/ui/app/toast.js';
|
||||||
import Plus from '@lucide/svelte/icons/plus';
|
import Plus from '@lucide/svelte/icons/plus';
|
||||||
import Trash2 from '@lucide/svelte/icons/trash-2';
|
import Trash2 from '@lucide/svelte/icons/trash-2';
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { Snippet } from 'svelte';
|
||||||
|
import PageHeader from '$lib/ui/app/page-header/page-header.svelte';
|
||||||
|
import PageShell from '$lib/components/page-shell.svelte';
|
||||||
|
import QueryState from '$lib/components/query-state.svelte';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
title: string;
|
||||||
|
description?: string;
|
||||||
|
actions?: Snippet;
|
||||||
|
data: unknown;
|
||||||
|
isLoading: boolean;
|
||||||
|
isError: boolean;
|
||||||
|
error?: unknown;
|
||||||
|
onRetry?: () => void;
|
||||||
|
skeleton?: Snippet;
|
||||||
|
content: Snippet;
|
||||||
|
};
|
||||||
|
|
||||||
|
let {
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
actions,
|
||||||
|
data,
|
||||||
|
isLoading,
|
||||||
|
isError,
|
||||||
|
error,
|
||||||
|
onRetry,
|
||||||
|
skeleton,
|
||||||
|
content
|
||||||
|
}: Props = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<PageShell>
|
||||||
|
<PageHeader {title} {description}>
|
||||||
|
{#snippet actions()}
|
||||||
|
{#if actions}
|
||||||
|
{@render actions()}
|
||||||
|
{/if}
|
||||||
|
{/snippet}
|
||||||
|
</PageHeader>
|
||||||
|
{#if isLoading}
|
||||||
|
{#if skeleton}
|
||||||
|
{@render skeleton()}
|
||||||
|
{/if}
|
||||||
|
{:else if isError}
|
||||||
|
<QueryState {data} isLoading={false} isError={true} {error} {onRetry} children={emptyChild} />
|
||||||
|
{:else}
|
||||||
|
{@render content()}
|
||||||
|
{/if}
|
||||||
|
</PageShell>
|
||||||
|
|
||||||
|
{#snippet emptyChild()}{/snippet}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { Snippet } from 'svelte';
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
CardDescription
|
||||||
|
} from '@evobgp/ui/components/card/index.js';
|
||||||
|
import { cn } from '$lib/utils.js';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
title?: string;
|
||||||
|
description?: string;
|
||||||
|
toolbar?: Snippet;
|
||||||
|
class?: string;
|
||||||
|
children: Snippet;
|
||||||
|
};
|
||||||
|
|
||||||
|
let { title, description, toolbar, class: className, children }: Props = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Card class={cn('gap-0 py-0', className)}>
|
||||||
|
{#if title || description || toolbar}
|
||||||
|
<CardHeader
|
||||||
|
class="flex flex-row flex-wrap items-start justify-between gap-2 border-b px-4 py-3"
|
||||||
|
>
|
||||||
|
<div class="flex min-w-0 flex-col gap-0.5">
|
||||||
|
{#if title}
|
||||||
|
<CardTitle class="text-base">{title}</CardTitle>
|
||||||
|
{/if}
|
||||||
|
{#if description}
|
||||||
|
<CardDescription>{description}</CardDescription>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{#if toolbar}
|
||||||
|
<div class="flex shrink-0 items-center gap-2">{@render toolbar()}</div>
|
||||||
|
{/if}
|
||||||
|
</CardHeader>
|
||||||
|
{/if}
|
||||||
|
<CardContent class="p-0">{@render children()}</CardContent>
|
||||||
|
</Card>
|
||||||
+7
-7
@@ -1,25 +1,25 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { apiMutate } from '$lib/api/client.js';
|
import { apiMutate } from '$lib/api/client.js';
|
||||||
import type { BgpCommunity, BgpCommunityCreate } from '$lib/api/types.js';
|
import type { BgpCommunity, BgpCommunityCreate } from '$lib/api/types.js';
|
||||||
import { Button } from '$lib/ui/core/button/index.js';
|
import { Button } from '@evobgp/ui/components/button/index.js';
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
CardDescription
|
CardDescription
|
||||||
} from '$lib/ui/core/card/index.js';
|
} from '@evobgp/ui/components/card/index.js';
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogFooter
|
DialogFooter
|
||||||
} from '$lib/ui/core/dialog/index.js';
|
} from '@evobgp/ui/components/dialog/index.js';
|
||||||
import FormField from '$lib/ui/patterns/form/form-field.svelte';
|
import FormField from '$lib/components/patterns/form/form-field.svelte';
|
||||||
import AppInput from '$lib/ui/patterns/form/app-input.svelte';
|
import AppInput from '$lib/components/patterns/form/app-input.svelte';
|
||||||
import AppDataTable from '$lib/ui/patterns/data-table/app-data-table.svelte';
|
import AppDataTable from '$lib/components/patterns/data-table/app-data-table.svelte';
|
||||||
import { confirm } from '$lib/ui/patterns/confirm/confirm-state.svelte.js';
|
import { confirm } from '$lib/components/patterns/confirm/confirm-state.svelte.js';
|
||||||
import { notify, notifyApiError } from '$lib/ui/app/toast.js';
|
import { notify, notifyApiError } from '$lib/ui/app/toast.js';
|
||||||
import Plus from '@lucide/svelte/icons/plus';
|
import Plus from '@lucide/svelte/icons/plus';
|
||||||
import Pencil from '@lucide/svelte/icons/pencil';
|
import Pencil from '@lucide/svelte/icons/pencil';
|
||||||
+7
-7
@@ -1,25 +1,25 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { apiMutate } from '$lib/api/client.js';
|
import { apiMutate } from '$lib/api/client.js';
|
||||||
import type { DohProfile, DohProfileCreate } from '$lib/api/types.js';
|
import type { DohProfile, DohProfileCreate } from '$lib/api/types.js';
|
||||||
import { Button } from '$lib/ui/core/button/index.js';
|
import { Button } from '@evobgp/ui/components/button/index.js';
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
CardDescription
|
CardDescription
|
||||||
} from '$lib/ui/core/card/index.js';
|
} from '@evobgp/ui/components/card/index.js';
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogFooter
|
DialogFooter
|
||||||
} from '$lib/ui/core/dialog/index.js';
|
} from '@evobgp/ui/components/dialog/index.js';
|
||||||
import FormField from '$lib/ui/patterns/form/form-field.svelte';
|
import FormField from '$lib/components/patterns/form/form-field.svelte';
|
||||||
import AppInput from '$lib/ui/patterns/form/app-input.svelte';
|
import AppInput from '$lib/components/patterns/form/app-input.svelte';
|
||||||
import AppDataTable from '$lib/ui/patterns/data-table/app-data-table.svelte';
|
import AppDataTable from '$lib/components/patterns/data-table/app-data-table.svelte';
|
||||||
import { confirm } from '$lib/ui/patterns/confirm/confirm-state.svelte.js';
|
import { confirm } from '$lib/components/patterns/confirm/confirm-state.svelte.js';
|
||||||
import { notify, notifyApiError } from '$lib/ui/app/toast.js';
|
import { notify, notifyApiError } from '$lib/ui/app/toast.js';
|
||||||
import Plus from '@lucide/svelte/icons/plus';
|
import Plus from '@lucide/svelte/icons/plus';
|
||||||
import Pencil from '@lucide/svelte/icons/pencil';
|
import Pencil from '@lucide/svelte/icons/pencil';
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { Snippet } from 'svelte';
|
||||||
|
import * as Sheet from '@evobgp/ui/components/sheet/index.js';
|
||||||
|
import { Button } from '@evobgp/ui/components/button/index.js';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
open: boolean;
|
||||||
|
title: string;
|
||||||
|
description?: string;
|
||||||
|
onOpenChange: (open: boolean) => void;
|
||||||
|
onSubmit?: () => void;
|
||||||
|
submitLabel?: string;
|
||||||
|
submitting?: boolean;
|
||||||
|
children: Snippet;
|
||||||
|
};
|
||||||
|
|
||||||
|
let {
|
||||||
|
open,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
onOpenChange,
|
||||||
|
onSubmit,
|
||||||
|
submitLabel = 'Сохранить',
|
||||||
|
submitting = false,
|
||||||
|
children
|
||||||
|
}: Props = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Sheet.Root {open} {onOpenChange}>
|
||||||
|
<Sheet.Content class="flex w-full flex-col gap-0 sm:max-w-lg">
|
||||||
|
<Sheet.Header>
|
||||||
|
<Sheet.Title>{title}</Sheet.Title>
|
||||||
|
{#if description}
|
||||||
|
<Sheet.Description>{description}</Sheet.Description>
|
||||||
|
{/if}
|
||||||
|
</Sheet.Header>
|
||||||
|
<div class="flex flex-1 flex-col gap-4 overflow-y-auto px-4 py-4">
|
||||||
|
{@render children()}
|
||||||
|
</div>
|
||||||
|
{#if onSubmit}
|
||||||
|
<Sheet.Footer>
|
||||||
|
<Button onclick={onSubmit} disabled={submitting}>{submitLabel}</Button>
|
||||||
|
</Sheet.Footer>
|
||||||
|
{/if}
|
||||||
|
</Sheet.Content>
|
||||||
|
</Sheet.Root>
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { Snippet } from 'svelte';
|
||||||
|
import { page } from '$app/state';
|
||||||
|
import { resolve } from '$app/paths';
|
||||||
|
import * as Sidebar from '@evobgp/ui/components/sidebar/index.js';
|
||||||
|
import * as Breadcrumb from '@evobgp/ui/components/breadcrumb/index.js';
|
||||||
|
import { Separator } from '@evobgp/ui/components/separator/index.js';
|
||||||
|
import type { ThemePreference } from '$lib/theme.js';
|
||||||
|
import { mainNav, bottomNav } from '$lib/ui/app/layout/nav.js';
|
||||||
|
import ThemeMenu from '$lib/ui/app/layout/theme-menu.svelte';
|
||||||
|
import AppVersion from '$lib/ui/app/layout/app-version.svelte';
|
||||||
|
import AppMobileNav from '$lib/ui/app/layout/app-mobile-nav.svelte';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
children: Snippet;
|
||||||
|
theme?: ThemePreference;
|
||||||
|
};
|
||||||
|
|
||||||
|
let { children, theme = $bindable<ThemePreference>('system') }: Props = $props();
|
||||||
|
|
||||||
|
let mobileNavOpen = $state(false);
|
||||||
|
|
||||||
|
const routeLabels: Record<string, string> = {
|
||||||
|
'/': 'Обзор',
|
||||||
|
...Object.fromEntries(mainNav.map((i) => [i.href, i.label])),
|
||||||
|
...Object.fromEntries(bottomNav.map((i) => [i.href, i.label]))
|
||||||
|
};
|
||||||
|
|
||||||
|
const breadcrumbLabel = $derived(routeLabels[page.url.pathname] ?? 'EvoBGP');
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const navHref = (href: string) => resolve(href as any);
|
||||||
|
|
||||||
|
function isActive(href: string) {
|
||||||
|
const pathname = page.url.pathname;
|
||||||
|
if (href === '/') return pathname === '/';
|
||||||
|
return pathname === href || pathname.startsWith(href + '/');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Sidebar.Provider>
|
||||||
|
<Sidebar.Root>
|
||||||
|
<Sidebar.Header class="border-b border-sidebar-border">
|
||||||
|
<div class="flex items-center gap-2 px-2 py-1">
|
||||||
|
<div class="flex min-w-0 flex-1 flex-col group-data-[collapsible=icon]:hidden">
|
||||||
|
<a href={navHref('/')} class="truncate font-semibold tracking-tight">EvoBGP</a>
|
||||||
|
<p class="truncate text-xs text-muted-foreground">Панель управления</p>
|
||||||
|
</div>
|
||||||
|
<ThemeMenu bind:theme />
|
||||||
|
</div>
|
||||||
|
</Sidebar.Header>
|
||||||
|
<Sidebar.Content>
|
||||||
|
<Sidebar.Group>
|
||||||
|
<Sidebar.GroupLabel>Операции</Sidebar.GroupLabel>
|
||||||
|
<Sidebar.GroupContent>
|
||||||
|
<Sidebar.Menu>
|
||||||
|
{#each mainNav as item (item.href)}
|
||||||
|
{@const Icon = item.icon}
|
||||||
|
<Sidebar.MenuItem>
|
||||||
|
<Sidebar.MenuButton isActive={isActive(item.href)}>
|
||||||
|
{#snippet child({ props })}
|
||||||
|
<a href={navHref(item.href)} {...props}>
|
||||||
|
<Icon class="size-4" />
|
||||||
|
<span>{item.label}</span>
|
||||||
|
</a>
|
||||||
|
{/snippet}
|
||||||
|
</Sidebar.MenuButton>
|
||||||
|
</Sidebar.MenuItem>
|
||||||
|
{/each}
|
||||||
|
</Sidebar.Menu>
|
||||||
|
</Sidebar.GroupContent>
|
||||||
|
</Sidebar.Group>
|
||||||
|
</Sidebar.Content>
|
||||||
|
<Sidebar.Footer class="border-t border-sidebar-border">
|
||||||
|
<Sidebar.Menu>
|
||||||
|
{#each bottomNav as item (item.href)}
|
||||||
|
{@const Icon = item.icon}
|
||||||
|
<Sidebar.MenuItem>
|
||||||
|
<Sidebar.MenuButton isActive={isActive(item.href)}>
|
||||||
|
{#snippet child({ props })}
|
||||||
|
<a href={navHref(item.href)} {...props}>
|
||||||
|
<Icon class="size-4" />
|
||||||
|
<span>{item.label}</span>
|
||||||
|
</a>
|
||||||
|
{/snippet}
|
||||||
|
</Sidebar.MenuButton>
|
||||||
|
</Sidebar.MenuItem>
|
||||||
|
{/each}
|
||||||
|
</Sidebar.Menu>
|
||||||
|
<AppVersion />
|
||||||
|
</Sidebar.Footer>
|
||||||
|
<Sidebar.Rail />
|
||||||
|
</Sidebar.Root>
|
||||||
|
<Sidebar.Inset>
|
||||||
|
<header
|
||||||
|
class="sticky top-0 z-10 flex h-14 shrink-0 items-center gap-2 border-b bg-background/95 px-4 backdrop-blur supports-[backdrop-filter]:bg-background/60"
|
||||||
|
>
|
||||||
|
<div class="flex items-center gap-2 md:hidden">
|
||||||
|
<AppMobileNav bind:open={mobileNavOpen} bind:theme />
|
||||||
|
<span class="font-semibold tracking-tight">EvoBGP</span>
|
||||||
|
</div>
|
||||||
|
<Sidebar.Trigger class="-ms-1 hidden md:flex" />
|
||||||
|
<Separator orientation="vertical" class="mx-2 hidden h-4 md:block" />
|
||||||
|
<Breadcrumb.Root class="hidden min-w-0 md:flex">
|
||||||
|
<Breadcrumb.List>
|
||||||
|
<Breadcrumb.Item>
|
||||||
|
<Breadcrumb.Page>{breadcrumbLabel}</Breadcrumb.Page>
|
||||||
|
</Breadcrumb.Item>
|
||||||
|
</Breadcrumb.List>
|
||||||
|
</Breadcrumb.Root>
|
||||||
|
</header>
|
||||||
|
<main class="flex flex-1 flex-col gap-4 p-4 md:gap-6 md:p-6">
|
||||||
|
{@render children()}
|
||||||
|
</main>
|
||||||
|
</Sidebar.Inset>
|
||||||
|
</Sidebar.Provider>
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { Snippet } from 'svelte';
|
||||||
|
import { Badge } from '@evobgp/ui/components/badge/index.js';
|
||||||
|
import { Button } from '@evobgp/ui/components/button/index.js';
|
||||||
|
import X from '@lucide/svelte/icons/x';
|
||||||
|
|
||||||
|
export type FilterChip = {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
chips?: FilterChip[];
|
||||||
|
onRemoveChip?: (id: string) => void;
|
||||||
|
onClear?: () => void;
|
||||||
|
children?: Snippet;
|
||||||
|
};
|
||||||
|
|
||||||
|
let { chips = [], onRemoveChip, onClear, children }: Props = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex flex-wrap items-center gap-2">
|
||||||
|
{#if children}
|
||||||
|
{@render children()}
|
||||||
|
{/if}
|
||||||
|
{#each chips as chip (chip.id)}
|
||||||
|
<Badge variant="secondary" class="gap-1 pr-1">
|
||||||
|
{chip.label}
|
||||||
|
{#if onRemoveChip}
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon-sm"
|
||||||
|
class="size-5"
|
||||||
|
onclick={() => onRemoveChip(chip.id)}
|
||||||
|
aria-label="Убрать фильтр {chip.label}"
|
||||||
|
>
|
||||||
|
<X class="size-3" />
|
||||||
|
</Button>
|
||||||
|
{/if}
|
||||||
|
</Badge>
|
||||||
|
{/each}
|
||||||
|
{#if chips.length > 0 && onClear}
|
||||||
|
<Button variant="ghost" size="sm" onclick={onClear}>Сбросить</Button>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
+5
-5
@@ -8,17 +8,17 @@
|
|||||||
supportsCsvIO
|
supportsCsvIO
|
||||||
} from '$lib/components/modules/module-helpers.js';
|
} from '$lib/components/modules/module-helpers.js';
|
||||||
|
|
||||||
import { Button } from '$lib/ui/core/button/index.js';
|
import { Button } from '@evobgp/ui/components/button/index.js';
|
||||||
import { Checkbox } from '$lib/ui/core/checkbox/index.js';
|
import { Checkbox } from '@evobgp/ui/components/checkbox/index.js';
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
CardDescription
|
CardDescription
|
||||||
} from '$lib/ui/core/card/index.js';
|
} from '@evobgp/ui/components/card/index.js';
|
||||||
import AppDataTable from '$lib/ui/patterns/data-table/app-data-table.svelte';
|
import AppDataTable from '$lib/components/patterns/data-table/app-data-table.svelte';
|
||||||
import { confirm } from '$lib/ui/patterns/confirm/confirm-state.svelte.js';
|
import { confirm } from '$lib/components/patterns/confirm/confirm-state.svelte.js';
|
||||||
import { notify, notifyApiError } from '$lib/ui/app/toast.js';
|
import { notify, notifyApiError } from '$lib/ui/app/toast.js';
|
||||||
import ModuleAsEntryDialog from '$lib/components/modules/ModuleAsEntryDialog.svelte';
|
import ModuleAsEntryDialog from '$lib/components/modules/ModuleAsEntryDialog.svelte';
|
||||||
import Plus from '@lucide/svelte/icons/plus';
|
import Plus from '@lucide/svelte/icons/plus';
|
||||||
+10
-5
@@ -8,9 +8,9 @@
|
|||||||
NONE_OPTION,
|
NONE_OPTION,
|
||||||
nullableSelectValue
|
nullableSelectValue
|
||||||
} from '$lib/components/modules/module-helpers.js';
|
} from '$lib/components/modules/module-helpers.js';
|
||||||
import { Button } from '$lib/ui/core/button/index.js';
|
import { Button } from '@evobgp/ui/components/button/index.js';
|
||||||
import { Input } from '$lib/ui/core/input/index.js';
|
import { Input } from '@evobgp/ui/components/input/index.js';
|
||||||
import { Label } from '$lib/ui/core/label/index.js';
|
import { Label } from '@evobgp/ui/components/label/index.js';
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@@ -18,8 +18,13 @@
|
|||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogFooter,
|
DialogFooter,
|
||||||
DialogDescription
|
DialogDescription
|
||||||
} from '$lib/ui/core/dialog/index.js';
|
} from '@evobgp/ui/components/dialog/index.js';
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger } from '$lib/ui/core/select/index.js';
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger
|
||||||
|
} from '@evobgp/ui/components/select/index.js';
|
||||||
import { notify, notifyApiError } from '$lib/ui/app/toast.js';
|
import { notify, notifyApiError } from '$lib/ui/app/toast.js';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
+10
-5
@@ -14,17 +14,22 @@
|
|||||||
NONE_OPTION,
|
NONE_OPTION,
|
||||||
nullableSelectValue
|
nullableSelectValue
|
||||||
} from '$lib/components/modules/module-helpers.js';
|
} from '$lib/components/modules/module-helpers.js';
|
||||||
import { Button } from '$lib/ui/core/button/index.js';
|
import { Button } from '@evobgp/ui/components/button/index.js';
|
||||||
import { Input } from '$lib/ui/core/input/index.js';
|
import { Input } from '@evobgp/ui/components/input/index.js';
|
||||||
import { Label } from '$lib/ui/core/label/index.js';
|
import { Label } from '@evobgp/ui/components/label/index.js';
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogFooter
|
DialogFooter
|
||||||
} from '$lib/ui/core/dialog/index.js';
|
} from '@evobgp/ui/components/dialog/index.js';
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger } from '$lib/ui/core/select/index.js';
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger
|
||||||
|
} from '@evobgp/ui/components/select/index.js';
|
||||||
import { notify, notifyApiError } from '$lib/ui/app/toast.js';
|
import { notify, notifyApiError } from '$lib/ui/app/toast.js';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
+6
-6
@@ -6,18 +6,18 @@
|
|||||||
communityLabel,
|
communityLabel,
|
||||||
normalizeCdnSourceKind
|
normalizeCdnSourceKind
|
||||||
} from '$lib/components/modules/module-helpers.js';
|
} from '$lib/components/modules/module-helpers.js';
|
||||||
import { Badge } from '$lib/ui/core/badge/index.js';
|
import { Badge } from '@evobgp/ui/components/badge/index.js';
|
||||||
import { Button } from '$lib/ui/core/button/index.js';
|
import { Button } from '@evobgp/ui/components/button/index.js';
|
||||||
import { Checkbox } from '$lib/ui/core/checkbox/index.js';
|
import { Checkbox } from '@evobgp/ui/components/checkbox/index.js';
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
CardDescription
|
CardDescription
|
||||||
} from '$lib/ui/core/card/index.js';
|
} from '@evobgp/ui/components/card/index.js';
|
||||||
import AppDataTable from '$lib/ui/patterns/data-table/app-data-table.svelte';
|
import AppDataTable from '$lib/components/patterns/data-table/app-data-table.svelte';
|
||||||
import { confirm } from '$lib/ui/patterns/confirm/confirm-state.svelte.js';
|
import { confirm } from '$lib/components/patterns/confirm/confirm-state.svelte.js';
|
||||||
import { notify, notifyApiError } from '$lib/ui/app/toast.js';
|
import { notify, notifyApiError } from '$lib/ui/app/toast.js';
|
||||||
import ModuleCdnSourceDialog from '$lib/components/modules/ModuleCdnSourceDialog.svelte';
|
import ModuleCdnSourceDialog from '$lib/components/modules/ModuleCdnSourceDialog.svelte';
|
||||||
import Plus from '@lucide/svelte/icons/plus';
|
import Plus from '@lucide/svelte/icons/plus';
|
||||||
+11
-6
@@ -2,9 +2,9 @@
|
|||||||
import { apiMutate } from '$lib/api/client.js';
|
import { apiMutate } from '$lib/api/client.js';
|
||||||
import type { ModuleCreate } from '$lib/api/types.js';
|
import type { ModuleCreate } from '$lib/api/types.js';
|
||||||
import { moduleTypeRu } from '$lib/ui-labels.js';
|
import { moduleTypeRu } from '$lib/ui-labels.js';
|
||||||
import { Button } from '$lib/ui/core/button/index.js';
|
import { Button } from '@evobgp/ui/components/button/index.js';
|
||||||
import { Input } from '$lib/ui/core/input/index.js';
|
import { Input } from '@evobgp/ui/components/input/index.js';
|
||||||
import { Label } from '$lib/ui/core/label/index.js';
|
import { Label } from '@evobgp/ui/components/label/index.js';
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@@ -12,9 +12,14 @@
|
|||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogFooter,
|
DialogFooter,
|
||||||
DialogDescription
|
DialogDescription
|
||||||
} from '$lib/ui/core/dialog/index.js';
|
} from '@evobgp/ui/components/dialog/index.js';
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger } from '$lib/ui/core/select/index.js';
|
import {
|
||||||
import { Switch } from '$lib/ui/core/switch/index.js';
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger
|
||||||
|
} from '@evobgp/ui/components/select/index.js';
|
||||||
|
import { Switch } from '@evobgp/ui/components/switch/index.js';
|
||||||
import { notify, notifyApiError } from '$lib/ui/app/toast.js';
|
import { notify, notifyApiError } from '$lib/ui/app/toast.js';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
+2
-2
@@ -2,8 +2,8 @@
|
|||||||
import { resolve } from '$app/paths';
|
import { resolve } from '$app/paths';
|
||||||
import type { ModuleRow } from '$lib/api/types.js';
|
import type { ModuleRow } from '$lib/api/types.js';
|
||||||
import { moduleEnabledRu, moduleEnabledBadgeVariant, moduleTypeRu } from '$lib/ui-labels.js';
|
import { moduleEnabledRu, moduleEnabledBadgeVariant, moduleTypeRu } from '$lib/ui-labels.js';
|
||||||
import { Badge } from '$lib/ui/core/badge/index.js';
|
import { Badge } from '@evobgp/ui/components/badge/index.js';
|
||||||
import { Button } from '$lib/ui/core/button/index.js';
|
import { Button } from '@evobgp/ui/components/button/index.js';
|
||||||
import PageHeader from '$lib/ui/app/page-header/page-header.svelte';
|
import PageHeader from '$lib/ui/app/page-header/page-header.svelte';
|
||||||
import ArrowLeft from '@lucide/svelte/icons/arrow-left';
|
import ArrowLeft from '@lucide/svelte/icons/arrow-left';
|
||||||
import Pencil from '@lucide/svelte/icons/pencil';
|
import Pencil from '@lucide/svelte/icons/pencil';
|
||||||
+5
-5
@@ -6,17 +6,17 @@
|
|||||||
sanitizeFilenamePart,
|
sanitizeFilenamePart,
|
||||||
supportsCsvIO
|
supportsCsvIO
|
||||||
} from '$lib/components/modules/module-helpers.js';
|
} from '$lib/components/modules/module-helpers.js';
|
||||||
import { Button } from '$lib/ui/core/button/index.js';
|
import { Button } from '@evobgp/ui/components/button/index.js';
|
||||||
import { Checkbox } from '$lib/ui/core/checkbox/index.js';
|
import { Checkbox } from '@evobgp/ui/components/checkbox/index.js';
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
CardDescription
|
CardDescription
|
||||||
} from '$lib/ui/core/card/index.js';
|
} from '@evobgp/ui/components/card/index.js';
|
||||||
import AppDataTable from '$lib/ui/patterns/data-table/app-data-table.svelte';
|
import AppDataTable from '$lib/components/patterns/data-table/app-data-table.svelte';
|
||||||
import { confirm } from '$lib/ui/patterns/confirm/confirm-state.svelte.js';
|
import { confirm } from '$lib/components/patterns/confirm/confirm-state.svelte.js';
|
||||||
import { notify, notifyApiError } from '$lib/ui/app/toast.js';
|
import { notify, notifyApiError } from '$lib/ui/app/toast.js';
|
||||||
import ModuleDomainEntryDialog from '$lib/components/modules/ModuleDomainEntryDialog.svelte';
|
import ModuleDomainEntryDialog from '$lib/components/modules/ModuleDomainEntryDialog.svelte';
|
||||||
import Plus from '@lucide/svelte/icons/plus';
|
import Plus from '@lucide/svelte/icons/plus';
|
||||||
+10
-5
@@ -8,17 +8,22 @@
|
|||||||
NONE_OPTION,
|
NONE_OPTION,
|
||||||
nullableSelectValue
|
nullableSelectValue
|
||||||
} from '$lib/components/modules/module-helpers.js';
|
} from '$lib/components/modules/module-helpers.js';
|
||||||
import { Button } from '$lib/ui/core/button/index.js';
|
import { Button } from '@evobgp/ui/components/button/index.js';
|
||||||
import { Input } from '$lib/ui/core/input/index.js';
|
import { Input } from '@evobgp/ui/components/input/index.js';
|
||||||
import { Label } from '$lib/ui/core/label/index.js';
|
import { Label } from '@evobgp/ui/components/label/index.js';
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogFooter
|
DialogFooter
|
||||||
} from '$lib/ui/core/dialog/index.js';
|
} from '@evobgp/ui/components/dialog/index.js';
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger } from '$lib/ui/core/select/index.js';
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger
|
||||||
|
} from '@evobgp/ui/components/select/index.js';
|
||||||
import { notify, notifyApiError } from '$lib/ui/app/toast.js';
|
import { notify, notifyApiError } from '$lib/ui/app/toast.js';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
+12
-7
@@ -16,19 +16,24 @@
|
|||||||
NONE_OPTION,
|
NONE_OPTION,
|
||||||
nullableSelectValue
|
nullableSelectValue
|
||||||
} from '$lib/components/modules/module-helpers.js';
|
} from '$lib/components/modules/module-helpers.js';
|
||||||
import { Button } from '$lib/ui/core/button/index.js';
|
import { Button } from '@evobgp/ui/components/button/index.js';
|
||||||
import { Input } from '$lib/ui/core/input/index.js';
|
import { Input } from '@evobgp/ui/components/input/index.js';
|
||||||
import { Label } from '$lib/ui/core/label/index.js';
|
import { Label } from '@evobgp/ui/components/label/index.js';
|
||||||
import { Checkbox } from '$lib/ui/core/checkbox/index.js';
|
import { Checkbox } from '@evobgp/ui/components/checkbox/index.js';
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogFooter
|
DialogFooter
|
||||||
} from '$lib/ui/core/dialog/index.js';
|
} from '@evobgp/ui/components/dialog/index.js';
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger } from '$lib/ui/core/select/index.js';
|
import {
|
||||||
import { Switch } from '$lib/ui/core/switch/index.js';
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger
|
||||||
|
} from '@evobgp/ui/components/select/index.js';
|
||||||
|
import { Switch } from '@evobgp/ui/components/switch/index.js';
|
||||||
import { notify, notifyApiError } from '$lib/ui/app/toast.js';
|
import { notify, notifyApiError } from '$lib/ui/app/toast.js';
|
||||||
|
|
||||||
const dohPolicyOptions: { value: DohResolverPolicy; label: string; hint: string }[] = [
|
const dohPolicyOptions: { value: DohResolverPolicy; label: string; hint: string }[] = [
|
||||||
+10
-5
@@ -2,17 +2,22 @@
|
|||||||
import { apiMutate } from '$lib/api/client.js';
|
import { apiMutate } from '$lib/api/client.js';
|
||||||
import type { BgpCommunity, IpRangeEntry, IpRangeEntryCreate } from '$lib/api/types.js';
|
import type { BgpCommunity, IpRangeEntry, IpRangeEntryCreate } from '$lib/api/types.js';
|
||||||
import { communityLabel, communityOptionLabel } from '$lib/components/modules/module-helpers.js';
|
import { communityLabel, communityOptionLabel } from '$lib/components/modules/module-helpers.js';
|
||||||
import { Button } from '$lib/ui/core/button/index.js';
|
import { Button } from '@evobgp/ui/components/button/index.js';
|
||||||
import { Input } from '$lib/ui/core/input/index.js';
|
import { Input } from '@evobgp/ui/components/input/index.js';
|
||||||
import { Label } from '$lib/ui/core/label/index.js';
|
import { Label } from '@evobgp/ui/components/label/index.js';
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogFooter
|
DialogFooter
|
||||||
} from '$lib/ui/core/dialog/index.js';
|
} from '@evobgp/ui/components/dialog/index.js';
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger } from '$lib/ui/core/select/index.js';
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger
|
||||||
|
} from '@evobgp/ui/components/select/index.js';
|
||||||
import { notify, notifyApiError } from '$lib/ui/app/toast.js';
|
import { notify, notifyApiError } from '$lib/ui/app/toast.js';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
+5
-5
@@ -6,17 +6,17 @@
|
|||||||
sanitizeFilenamePart,
|
sanitizeFilenamePart,
|
||||||
supportsCsvIO
|
supportsCsvIO
|
||||||
} from '$lib/components/modules/module-helpers.js';
|
} from '$lib/components/modules/module-helpers.js';
|
||||||
import { Button } from '$lib/ui/core/button/index.js';
|
import { Button } from '@evobgp/ui/components/button/index.js';
|
||||||
import { Checkbox } from '$lib/ui/core/checkbox/index.js';
|
import { Checkbox } from '@evobgp/ui/components/checkbox/index.js';
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
CardDescription
|
CardDescription
|
||||||
} from '$lib/ui/core/card/index.js';
|
} from '@evobgp/ui/components/card/index.js';
|
||||||
import AppDataTable from '$lib/ui/patterns/data-table/app-data-table.svelte';
|
import AppDataTable from '$lib/components/patterns/data-table/app-data-table.svelte';
|
||||||
import { confirm } from '$lib/ui/patterns/confirm/confirm-state.svelte.js';
|
import { confirm } from '$lib/components/patterns/confirm/confirm-state.svelte.js';
|
||||||
import { notify, notifyApiError } from '$lib/ui/app/toast.js';
|
import { notify, notifyApiError } from '$lib/ui/app/toast.js';
|
||||||
import ModuleIpRangeEntryDialog from '$lib/components/modules/ModuleIpRangeEntryDialog.svelte';
|
import ModuleIpRangeEntryDialog from '$lib/components/modules/ModuleIpRangeEntryDialog.svelte';
|
||||||
import Plus from '@lucide/svelte/icons/plus';
|
import Plus from '@lucide/svelte/icons/plus';
|
||||||
+2
-2
@@ -7,8 +7,8 @@
|
|||||||
dohProfileLabel,
|
dohProfileLabel,
|
||||||
moduleDohProfileIds
|
moduleDohProfileIds
|
||||||
} from '$lib/components/modules/module-helpers.js';
|
} from '$lib/components/modules/module-helpers.js';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '$lib/ui/core/card/index.js';
|
import { Card, CardContent, CardHeader, CardTitle } from '@evobgp/ui/components/card/index.js';
|
||||||
import CardSkeleton from '$lib/ui/patterns/feedback/card-skeleton.svelte';
|
import CardSkeleton from '$lib/components/patterns/feedback/card-skeleton.svelte';
|
||||||
import { cn } from '$lib/utils.js';
|
import { cn } from '$lib/utils.js';
|
||||||
import ArrowDownUp from '@lucide/svelte/icons/arrow-down-up';
|
import ArrowDownUp from '@lucide/svelte/icons/arrow-down-up';
|
||||||
import Timer from '@lucide/svelte/icons/timer';
|
import Timer from '@lucide/svelte/icons/timer';
|
||||||
+2
-2
@@ -1,14 +1,14 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { resolve } from '$app/paths';
|
import { resolve } from '$app/paths';
|
||||||
import type { AsEntry, CdnSource, DomainEntry, IpRangeEntry, ModuleRow } from '$lib/api/types.js';
|
import type { AsEntry, CdnSource, DomainEntry, IpRangeEntry, ModuleRow } from '$lib/api/types.js';
|
||||||
import { Button } from '$lib/ui/core/button/index.js';
|
import { Button } from '@evobgp/ui/components/button/index.js';
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
CardDescription
|
CardDescription
|
||||||
} from '$lib/ui/core/card/index.js';
|
} from '@evobgp/ui/components/card/index.js';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
mod: ModuleRow;
|
mod: ModuleRow;
|
||||||
+13
-13
@@ -35,31 +35,31 @@
|
|||||||
type ScheduleEditor,
|
type ScheduleEditor,
|
||||||
type ScheduleMode
|
type ScheduleMode
|
||||||
} from '$lib/maintenance/policy-schedule.js';
|
} from '$lib/maintenance/policy-schedule.js';
|
||||||
import { Button } from '$lib/ui/core/button/index.js';
|
import { Button } from '@evobgp/ui/components/button/index.js';
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
CardDescription
|
CardDescription
|
||||||
} from '$lib/ui/core/card/index.js';
|
} from '@evobgp/ui/components/card/index.js';
|
||||||
import { Badge } from '$lib/ui/core/badge/index.js';
|
import { Badge } from '@evobgp/ui/components/badge/index.js';
|
||||||
import { Alert, AlertDescription, AlertTitle } from '$lib/ui/core/alert/index.js';
|
import { Alert, AlertDescription, AlertTitle } from '@evobgp/ui/components/alert/index.js';
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogFooter
|
DialogFooter
|
||||||
} from '$lib/ui/core/dialog/index.js';
|
} from '@evobgp/ui/components/dialog/index.js';
|
||||||
import { Switch } from '$lib/ui/core/switch/index.js';
|
import { Switch } from '@evobgp/ui/components/switch/index.js';
|
||||||
import { Label } from '$lib/ui/core/label/index.js';
|
import { Label } from '@evobgp/ui/components/label/index.js';
|
||||||
import { Checkbox } from '$lib/ui/core/checkbox/index.js';
|
import { Checkbox } from '@evobgp/ui/components/checkbox/index.js';
|
||||||
import FormField from '$lib/ui/patterns/form/form-field.svelte';
|
import FormField from '$lib/components/patterns/form/form-field.svelte';
|
||||||
import AppInput from '$lib/ui/patterns/form/app-input.svelte';
|
import AppInput from '$lib/components/patterns/form/app-input.svelte';
|
||||||
import AppDataTable from '$lib/ui/patterns/data-table/app-data-table.svelte';
|
import AppDataTable from '$lib/components/patterns/data-table/app-data-table.svelte';
|
||||||
import type { DataTableColumn } from '$lib/ui/patterns/data-table/types.js';
|
import type { DataTableColumn } from '$lib/components/patterns/data-table/types.js';
|
||||||
import { confirm } from '$lib/ui/patterns/confirm/confirm-state.svelte.js';
|
import { confirm } from '$lib/components/patterns/confirm/confirm-state.svelte.js';
|
||||||
import { notify, notifyApiError } from '$lib/ui/app/toast.js';
|
import { notify, notifyApiError } from '$lib/ui/app/toast.js';
|
||||||
import Plus from '@lucide/svelte/icons/plus';
|
import Plus from '@lucide/svelte/icons/plus';
|
||||||
import Pencil from '@lucide/svelte/icons/pencil';
|
import Pencil from '@lucide/svelte/icons/pencil';
|
||||||
+8
-8
@@ -17,17 +17,17 @@
|
|||||||
type PostgresMaintLog,
|
type PostgresMaintLog,
|
||||||
type CorrelationResponse
|
type CorrelationResponse
|
||||||
} from '$lib/monitoring/postgres.js';
|
} from '$lib/monitoring/postgres.js';
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '$lib/ui/core/tabs/index.js';
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@evobgp/ui/components/tabs/index.js';
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
CardDescription
|
CardDescription
|
||||||
} from '$lib/ui/core/card/index.js';
|
} from '@evobgp/ui/components/card/index.js';
|
||||||
import { Button } from '$lib/ui/core/button/index.js';
|
import { Button } from '@evobgp/ui/components/button/index.js';
|
||||||
import { Badge } from '$lib/ui/core/badge/index.js';
|
import { Badge } from '@evobgp/ui/components/badge/index.js';
|
||||||
import { Alert, AlertDescription, AlertTitle } from '$lib/ui/core/alert/index.js';
|
import { Alert, AlertDescription, AlertTitle } from '@evobgp/ui/components/alert/index.js';
|
||||||
import {
|
import {
|
||||||
Table,
|
Table,
|
||||||
TableBody,
|
TableBody,
|
||||||
@@ -35,9 +35,9 @@
|
|||||||
TableHead,
|
TableHead,
|
||||||
TableHeader,
|
TableHeader,
|
||||||
TableRow
|
TableRow
|
||||||
} from '$lib/ui/core/table/index.js';
|
} from '@evobgp/ui/components/table/index.js';
|
||||||
import { Switch } from '$lib/ui/core/switch/index.js';
|
import { Switch } from '@evobgp/ui/components/switch/index.js';
|
||||||
import { Label } from '$lib/ui/core/label/index.js';
|
import { Label } from '@evobgp/ui/components/label/index.js';
|
||||||
import Database from '@lucide/svelte/icons/database';
|
import Database from '@lucide/svelte/icons/database';
|
||||||
import RefreshCw from '@lucide/svelte/icons/refresh-cw';
|
import RefreshCw from '@lucide/svelte/icons/refresh-cw';
|
||||||
|
|
||||||
+10
-10
@@ -14,33 +14,33 @@
|
|||||||
} from '$lib/runtime-logs/runtime-logs-api.js';
|
} from '$lib/runtime-logs/runtime-logs-api.js';
|
||||||
import { formatBytes } from '$lib/monitoring/postgres.js';
|
import { formatBytes } from '$lib/monitoring/postgres.js';
|
||||||
import { formatDateTime } from '$lib/modules/display.js';
|
import { formatDateTime } from '$lib/modules/display.js';
|
||||||
import { Button } from '$lib/ui/core/button/index.js';
|
import { Button } from '@evobgp/ui/components/button/index.js';
|
||||||
import { Badge } from '$lib/ui/core/badge/index.js';
|
import { Badge } from '@evobgp/ui/components/badge/index.js';
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
CardDescription,
|
CardDescription,
|
||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle
|
CardTitle
|
||||||
} from '$lib/ui/core/card/index.js';
|
} from '@evobgp/ui/components/card/index.js';
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogDescription,
|
DialogDescription,
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle
|
DialogTitle
|
||||||
} from '$lib/ui/core/dialog/index.js';
|
} from '@evobgp/ui/components/dialog/index.js';
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuTrigger
|
DropdownMenuTrigger
|
||||||
} from '$lib/ui/core/dropdown-menu/index.js';
|
} from '@evobgp/ui/components/dropdown-menu/index.js';
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '$lib/ui/core/tabs/index.js';
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@evobgp/ui/components/tabs/index.js';
|
||||||
import AppDataTable from '$lib/ui/patterns/data-table/app-data-table.svelte';
|
import AppDataTable from '$lib/components/patterns/data-table/app-data-table.svelte';
|
||||||
import type { DataTableColumn } from '$lib/ui/patterns/data-table/types.js';
|
import type { DataTableColumn } from '$lib/components/patterns/data-table/types.js';
|
||||||
import EmptyState from '$lib/ui/patterns/empty-state/empty-state.svelte';
|
import EmptyState from '$lib/components/patterns/empty-state/empty-state.svelte';
|
||||||
import { confirm } from '$lib/ui/patterns/confirm/confirm-state.svelte.js';
|
import { confirm } from '$lib/components/patterns/confirm/confirm-state.svelte.js';
|
||||||
import ScrollPreBlock from '$lib/components/app/scroll-pre-block.svelte';
|
import ScrollPreBlock from '$lib/components/app/scroll-pre-block.svelte';
|
||||||
import { notify, notifyApiError } from '$lib/ui/app/toast.js';
|
import { notify, notifyApiError } from '$lib/ui/app/toast.js';
|
||||||
import {
|
import {
|
||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Label } from '$lib/ui/core/label/index.js';
|
import { Label } from '@evobgp/ui/components/label/index.js';
|
||||||
import { Switch } from '$lib/ui/core/switch/index.js';
|
import { Switch } from '@evobgp/ui/components/switch/index.js';
|
||||||
import { readNetworkAutoRefresh, writeNetworkAutoRefresh } from '$lib/network/network-metrics.js';
|
import { readNetworkAutoRefresh, writeNetworkAutoRefresh } from '$lib/network/network-metrics.js';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
+2
-2
@@ -3,14 +3,14 @@
|
|||||||
import { resolve } from '$app/paths';
|
import { resolve } from '$app/paths';
|
||||||
import { loadSettings, partitionSettings } from '$lib/settings/settings-api.js';
|
import { loadSettings, partitionSettings } from '$lib/settings/settings-api.js';
|
||||||
import { BIRD_SETTING_KEYS } from '$lib/settings/settings-known-keys.js';
|
import { BIRD_SETTING_KEYS } from '$lib/settings/settings-known-keys.js';
|
||||||
import { Button } from '$lib/ui/core/button/index.js';
|
import { Button } from '@evobgp/ui/components/button/index.js';
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
CardDescription,
|
CardDescription,
|
||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle
|
CardTitle
|
||||||
} from '$lib/ui/core/card/index.js';
|
} from '@evobgp/ui/components/card/index.js';
|
||||||
import { notifyApiError } from '$lib/ui/app/toast.js';
|
import { notifyApiError } from '$lib/ui/app/toast.js';
|
||||||
import ArrowRight from '@lucide/svelte/icons/arrow-right';
|
import ArrowRight from '@lucide/svelte/icons/arrow-right';
|
||||||
import SlidersHorizontal from '@lucide/svelte/icons/sliders-horizontal';
|
import SlidersHorizontal from '@lucide/svelte/icons/sliders-horizontal';
|
||||||
+3
-3
@@ -8,9 +8,9 @@
|
|||||||
networkOverallStatusHint,
|
networkOverallStatusHint,
|
||||||
networkOverallStatusLabel
|
networkOverallStatusLabel
|
||||||
} from '$lib/network/network-metrics.js';
|
} from '$lib/network/network-metrics.js';
|
||||||
import { Alert, AlertDescription, AlertTitle } from '$lib/ui/core/alert/index.js';
|
import { Alert, AlertDescription, AlertTitle } from '@evobgp/ui/components/alert/index.js';
|
||||||
import { Button } from '$lib/ui/core/button/index.js';
|
import { Button } from '@evobgp/ui/components/button/index.js';
|
||||||
import KpiMetricsGrid from '$lib/ui/patterns/kpi/kpi-metrics-grid.svelte';
|
import KpiMetricsGrid from '$lib/components/patterns/kpi/kpi-metrics-grid.svelte';
|
||||||
import NetworkSpeakerStatusCard from '$lib/components/network/NetworkSpeakerStatusCard.svelte';
|
import NetworkSpeakerStatusCard from '$lib/components/network/NetworkSpeakerStatusCard.svelte';
|
||||||
import CheckCircle from '@lucide/svelte/icons/check-circle';
|
import CheckCircle from '@lucide/svelte/icons/check-circle';
|
||||||
import AlertTriangle from '@lucide/svelte/icons/alert-triangle';
|
import AlertTriangle from '@lucide/svelte/icons/alert-triangle';
|
||||||
+16
-11
@@ -1,16 +1,16 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { apiMutate } from '$lib/api/client.js';
|
import { apiMutate } from '$lib/api/client.js';
|
||||||
import type { PeerRow, BgpPeerCreate, SpeakerRow, PeerSessionOnSpeaker } from '$lib/api/types.js';
|
import type { PeerRow, BgpPeerCreate, SpeakerRow, PeerSessionOnSpeaker } from '$lib/api/types.js';
|
||||||
import { Badge } from '$lib/ui/core/badge/index.js';
|
import { Badge } from '@evobgp/ui/components/badge/index.js';
|
||||||
import { Button } from '$lib/ui/core/button/index.js';
|
import { Button } from '@evobgp/ui/components/button/index.js';
|
||||||
import { Label } from '$lib/ui/core/label/index.js';
|
import { Label } from '@evobgp/ui/components/label/index.js';
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
CardDescription
|
CardDescription
|
||||||
} from '$lib/ui/core/card/index.js';
|
} from '@evobgp/ui/components/card/index.js';
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@@ -18,13 +18,18 @@
|
|||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogFooter,
|
DialogFooter,
|
||||||
DialogDescription
|
DialogDescription
|
||||||
} from '$lib/ui/core/dialog/index.js';
|
} from '@evobgp/ui/components/dialog/index.js';
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger } from '$lib/ui/core/select/index.js';
|
import {
|
||||||
import { Switch } from '$lib/ui/core/switch/index.js';
|
Select,
|
||||||
import FormField from '$lib/ui/patterns/form/form-field.svelte';
|
SelectContent,
|
||||||
import AppInput from '$lib/ui/patterns/form/app-input.svelte';
|
SelectItem,
|
||||||
import AppDataTable from '$lib/ui/patterns/data-table/app-data-table.svelte';
|
SelectTrigger
|
||||||
import { confirm } from '$lib/ui/patterns/confirm/confirm-state.svelte.js';
|
} from '@evobgp/ui/components/select/index.js';
|
||||||
|
import { Switch } from '@evobgp/ui/components/switch/index.js';
|
||||||
|
import FormField from '$lib/components/patterns/form/form-field.svelte';
|
||||||
|
import AppInput from '$lib/components/patterns/form/app-input.svelte';
|
||||||
|
import AppDataTable from '$lib/components/patterns/data-table/app-data-table.svelte';
|
||||||
|
import { confirm } from '$lib/components/patterns/confirm/confirm-state.svelte.js';
|
||||||
import { notify, notifyApiError } from '$lib/ui/app/toast.js';
|
import { notify, notifyApiError } from '$lib/ui/app/toast.js';
|
||||||
import Plus from '@lucide/svelte/icons/plus';
|
import Plus from '@lucide/svelte/icons/plus';
|
||||||
import Pencil from '@lucide/svelte/icons/pencil';
|
import Pencil from '@lucide/svelte/icons/pencil';
|
||||||
+6
-6
@@ -9,17 +9,17 @@
|
|||||||
speakerLiveAgentError,
|
speakerLiveAgentError,
|
||||||
speakerLiveBgpError
|
speakerLiveBgpError
|
||||||
} from '$lib/network/network-metrics.js';
|
} from '$lib/network/network-metrics.js';
|
||||||
import { Alert, AlertDescription, AlertTitle } from '$lib/ui/core/alert/index.js';
|
import { Alert, AlertDescription, AlertTitle } from '@evobgp/ui/components/alert/index.js';
|
||||||
import { Badge } from '$lib/ui/core/badge/index.js';
|
import { Badge } from '@evobgp/ui/components/badge/index.js';
|
||||||
import { Button } from '$lib/ui/core/button/index.js';
|
import { Button } from '@evobgp/ui/components/button/index.js';
|
||||||
import { Separator } from '$lib/ui/core/separator/index.js';
|
import { Separator } from '@evobgp/ui/components/separator/index.js';
|
||||||
import {
|
import {
|
||||||
Sheet,
|
Sheet,
|
||||||
SheetContent,
|
SheetContent,
|
||||||
SheetDescription,
|
SheetDescription,
|
||||||
SheetHeader,
|
SheetHeader,
|
||||||
SheetTitle
|
SheetTitle
|
||||||
} from '$lib/ui/core/sheet/index.js';
|
} from '@evobgp/ui/components/sheet/index.js';
|
||||||
import {
|
import {
|
||||||
Table,
|
Table,
|
||||||
TableBody,
|
TableBody,
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
TableHead,
|
TableHead,
|
||||||
TableHeader,
|
TableHeader,
|
||||||
TableRow
|
TableRow
|
||||||
} from '$lib/ui/core/table/index.js';
|
} from '@evobgp/ui/components/table/index.js';
|
||||||
import AlertTriangle from '@lucide/svelte/icons/alert-triangle';
|
import AlertTriangle from '@lucide/svelte/icons/alert-triangle';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
+2
-2
@@ -6,14 +6,14 @@
|
|||||||
speakerHasDrift,
|
speakerHasDrift,
|
||||||
speakerLabel
|
speakerLabel
|
||||||
} from '$lib/network/network-metrics.js';
|
} from '$lib/network/network-metrics.js';
|
||||||
import { Badge } from '$lib/ui/core/badge/index.js';
|
import { Badge } from '@evobgp/ui/components/badge/index.js';
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
CardDescription
|
CardDescription
|
||||||
} from '$lib/ui/core/card/index.js';
|
} from '@evobgp/ui/components/card/index.js';
|
||||||
import { cn } from '$lib/utils.js';
|
import { cn } from '$lib/utils.js';
|
||||||
import Server from '@lucide/svelte/icons/server';
|
import Server from '@lucide/svelte/icons/server';
|
||||||
|
|
||||||
+9
-9
@@ -6,15 +6,15 @@
|
|||||||
speakerDisplayStatus,
|
speakerDisplayStatus,
|
||||||
speakerHasDrift
|
speakerHasDrift
|
||||||
} from '$lib/network/network-metrics.js';
|
} from '$lib/network/network-metrics.js';
|
||||||
import { Badge } from '$lib/ui/core/badge/index.js';
|
import { Badge } from '@evobgp/ui/components/badge/index.js';
|
||||||
import { Button } from '$lib/ui/core/button/index.js';
|
import { Button } from '@evobgp/ui/components/button/index.js';
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
CardDescription
|
CardDescription
|
||||||
} from '$lib/ui/core/card/index.js';
|
} from '@evobgp/ui/components/card/index.js';
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@@ -22,12 +22,12 @@
|
|||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogFooter,
|
DialogFooter,
|
||||||
DialogDescription
|
DialogDescription
|
||||||
} from '$lib/ui/core/dialog/index.js';
|
} from '@evobgp/ui/components/dialog/index.js';
|
||||||
import { Checkbox } from '$lib/ui/core/checkbox/index.js';
|
import { Checkbox } from '@evobgp/ui/components/checkbox/index.js';
|
||||||
import FormField from '$lib/ui/patterns/form/form-field.svelte';
|
import FormField from '$lib/components/patterns/form/form-field.svelte';
|
||||||
import AppInput from '$lib/ui/patterns/form/app-input.svelte';
|
import AppInput from '$lib/components/patterns/form/app-input.svelte';
|
||||||
import AppDataTable from '$lib/ui/patterns/data-table/app-data-table.svelte';
|
import AppDataTable from '$lib/components/patterns/data-table/app-data-table.svelte';
|
||||||
import { confirm } from '$lib/ui/patterns/confirm/confirm-state.svelte.js';
|
import { confirm } from '$lib/components/patterns/confirm/confirm-state.svelte.js';
|
||||||
import { notify, notifyApiError } from '$lib/ui/app/toast.js';
|
import { notify, notifyApiError } from '$lib/ui/app/toast.js';
|
||||||
import Plus from '@lucide/svelte/icons/plus';
|
import Plus from '@lucide/svelte/icons/plus';
|
||||||
import Pencil from '@lucide/svelte/icons/pencil';
|
import Pencil from '@lucide/svelte/icons/pencil';
|
||||||
+8
-3
@@ -1,14 +1,19 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { RevisionDiff, RevisionPrefix, RevisionRow } from '$lib/api/types.js';
|
import type { RevisionDiff, RevisionPrefix, RevisionRow } from '$lib/api/types.js';
|
||||||
import { Button } from '$lib/ui/core/button/index.js';
|
import { Button } from '@evobgp/ui/components/button/index.js';
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
CardDescription,
|
CardDescription,
|
||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle
|
CardTitle
|
||||||
} from '$lib/ui/core/card/index.js';
|
} from '@evobgp/ui/components/card/index.js';
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger } from '$lib/ui/core/select/index.js';
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger
|
||||||
|
} from '@evobgp/ui/components/select/index.js';
|
||||||
import { formatDateTime } from '$lib/modules/display.js';
|
import { formatDateTime } from '$lib/modules/display.js';
|
||||||
import { sortRevisionDiffItems } from '$lib/sort-prefixes.js';
|
import { sortRevisionDiffItems } from '$lib/sort-prefixes.js';
|
||||||
|
|
||||||
+3
-3
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Button } from '$lib/ui/core/button/index.js';
|
import { Button } from '@evobgp/ui/components/button/index.js';
|
||||||
import { Input } from '$lib/ui/core/input/index.js';
|
import { Input } from '@evobgp/ui/components/input/index.js';
|
||||||
import { Label } from '$lib/ui/core/label/index.js';
|
import { Label } from '@evobgp/ui/components/label/index.js';
|
||||||
import Filter from '@lucide/svelte/icons/filter';
|
import Filter from '@lucide/svelte/icons/filter';
|
||||||
import Search from '@lucide/svelte/icons/search';
|
import Search from '@lucide/svelte/icons/search';
|
||||||
import X from '@lucide/svelte/icons/x';
|
import X from '@lucide/svelte/icons/x';
|
||||||
+4
-4
@@ -2,16 +2,16 @@
|
|||||||
import { SvelteMap, SvelteSet } from 'svelte/reactivity';
|
import { SvelteMap, SvelteSet } from 'svelte/reactivity';
|
||||||
import type { JobRow } from '$lib/api/types.js';
|
import type { JobRow } from '$lib/api/types.js';
|
||||||
import type { JobDetailedReport, JobLogEntry } from './types.js';
|
import type { JobDetailedReport, JobLogEntry } from './types.js';
|
||||||
import { Badge } from '$lib/ui/core/badge/index.js';
|
import { Badge } from '@evobgp/ui/components/badge/index.js';
|
||||||
import { Button } from '$lib/ui/core/button/index.js';
|
import { Button } from '@evobgp/ui/components/button/index.js';
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
CardDescription,
|
CardDescription,
|
||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle
|
CardTitle
|
||||||
} from '$lib/ui/core/card/index.js';
|
} from '@evobgp/ui/components/card/index.js';
|
||||||
import EmptyState from '$lib/ui/patterns/empty-state/empty-state.svelte';
|
import EmptyState from '$lib/components/patterns/empty-state/empty-state.svelte';
|
||||||
import { formatDateTime } from '$lib/modules/display.js';
|
import { formatDateTime } from '$lib/modules/display.js';
|
||||||
import RefreshCw from '@lucide/svelte/icons/refresh-cw';
|
import RefreshCw from '@lucide/svelte/icons/refresh-cw';
|
||||||
import Eye from '@lucide/svelte/icons/eye';
|
import Eye from '@lucide/svelte/icons/eye';
|
||||||
+3
-3
@@ -1,8 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { BirdStatus } from '$lib/api/types.js';
|
import type { BirdStatus } from '$lib/api/types.js';
|
||||||
import { Badge } from '$lib/ui/core/badge/index.js';
|
import { Badge } from '@evobgp/ui/components/badge/index.js';
|
||||||
import { Button } from '$lib/ui/core/button/index.js';
|
import { Button } from '@evobgp/ui/components/button/index.js';
|
||||||
import { Card } from '$lib/ui/core/card/index.js';
|
import { Card } from '@evobgp/ui/components/card/index.js';
|
||||||
import RefreshCw from '@lucide/svelte/icons/refresh-cw';
|
import RefreshCw from '@lucide/svelte/icons/refresh-cw';
|
||||||
import Play from '@lucide/svelte/icons/play';
|
import Play from '@lucide/svelte/icons/play';
|
||||||
import RotateCcw from '@lucide/svelte/icons/rotate-ccw';
|
import RotateCcw from '@lucide/svelte/icons/rotate-ccw';
|
||||||
+3
-3
@@ -1,14 +1,14 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { RevisionRow } from '$lib/api/types.js';
|
import type { RevisionRow } from '$lib/api/types.js';
|
||||||
import { Button } from '$lib/ui/core/button/index.js';
|
import { Button } from '@evobgp/ui/components/button/index.js';
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
CardDescription,
|
CardDescription,
|
||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle
|
CardTitle
|
||||||
} from '$lib/ui/core/card/index.js';
|
} from '@evobgp/ui/components/card/index.js';
|
||||||
import AppDataTable from '$lib/ui/patterns/data-table/app-data-table.svelte';
|
import AppDataTable from '$lib/components/patterns/data-table/app-data-table.svelte';
|
||||||
import { formatDateTime } from '$lib/modules/display.js';
|
import { formatDateTime } from '$lib/modules/display.js';
|
||||||
import RefreshCw from '@lucide/svelte/icons/refresh-cw';
|
import RefreshCw from '@lucide/svelte/icons/refresh-cw';
|
||||||
import Undo from '@lucide/svelte/icons/undo';
|
import Undo from '@lucide/svelte/icons/undo';
|
||||||
+3
-3
@@ -6,9 +6,9 @@
|
|||||||
getCoreRowModel,
|
getCoreRowModel,
|
||||||
getPaginationRowModel
|
getPaginationRowModel
|
||||||
} from '@tanstack/table-core';
|
} from '@tanstack/table-core';
|
||||||
import { createSvelteTable, FlexRender } from '$lib/ui/core/data-table/index.js';
|
import { createSvelteTable, FlexRender } from '@evobgp/ui/components/data-table/index.js';
|
||||||
import * as Table from '$lib/ui/core/table/index.js';
|
import * as Table from '@evobgp/ui/components/table/index.js';
|
||||||
import { Button } from '$lib/ui/core/button/index.js';
|
import { Button } from '@evobgp/ui/components/button/index.js';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
rows: RowData[];
|
rows: RowData[];
|
||||||
+3
-3
@@ -8,15 +8,15 @@
|
|||||||
networkOverallStatusHint,
|
networkOverallStatusHint,
|
||||||
networkOverallStatusLabel
|
networkOverallStatusLabel
|
||||||
} from '$lib/network/network-metrics.js';
|
} from '$lib/network/network-metrics.js';
|
||||||
import { Alert, AlertDescription, AlertTitle } from '$lib/ui/core/alert/index.js';
|
import { Alert, AlertDescription, AlertTitle } from '@evobgp/ui/components/alert/index.js';
|
||||||
import { Button } from '$lib/ui/core/button/index.js';
|
import { Button } from '@evobgp/ui/components/button/index.js';
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
CardDescription
|
CardDescription
|
||||||
} from '$lib/ui/core/card/index.js';
|
} from '@evobgp/ui/components/card/index.js';
|
||||||
import CheckCircle from '@lucide/svelte/icons/check-circle';
|
import CheckCircle from '@lucide/svelte/icons/check-circle';
|
||||||
import AlertTriangle from '@lucide/svelte/icons/alert-triangle';
|
import AlertTriangle from '@lucide/svelte/icons/alert-triangle';
|
||||||
import XCircle from '@lucide/svelte/icons/x-circle';
|
import XCircle from '@lucide/svelte/icons/x-circle';
|
||||||
+4
-4
@@ -4,16 +4,16 @@
|
|||||||
import { formatDateTime } from '$lib/modules/display.js';
|
import { formatDateTime } from '$lib/modules/display.js';
|
||||||
import { jobKindTitle } from '$lib/operations/job-kind-label.js';
|
import { jobKindTitle } from '$lib/operations/job-kind-label.js';
|
||||||
import { jobStatusRu, jobStatusBadgeVariant } from '$lib/ui-labels.js';
|
import { jobStatusRu, jobStatusBadgeVariant } from '$lib/ui-labels.js';
|
||||||
import { Badge } from '$lib/ui/core/badge/index.js';
|
import { Badge } from '@evobgp/ui/components/badge/index.js';
|
||||||
import { Button } from '$lib/ui/core/button/index.js';
|
import { Button } from '@evobgp/ui/components/button/index.js';
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
CardDescription
|
CardDescription
|
||||||
} from '$lib/ui/core/card/index.js';
|
} from '@evobgp/ui/components/card/index.js';
|
||||||
import AppDataTable from '$lib/ui/patterns/data-table/app-data-table.svelte';
|
import AppDataTable from '$lib/components/patterns/data-table/app-data-table.svelte';
|
||||||
import ArrowRight from '@lucide/svelte/icons/arrow-right';
|
import ArrowRight from '@lucide/svelte/icons/arrow-right';
|
||||||
import ExternalLink from '@lucide/svelte/icons/external-link';
|
import ExternalLink from '@lucide/svelte/icons/external-link';
|
||||||
|
|
||||||
+3
-3
@@ -2,15 +2,15 @@
|
|||||||
import { resolve } from '$app/paths';
|
import { resolve } from '$app/paths';
|
||||||
import type { RevisionRow } from '$lib/api/types.js';
|
import type { RevisionRow } from '$lib/api/types.js';
|
||||||
import { formatDateTime } from '$lib/modules/display.js';
|
import { formatDateTime } from '$lib/modules/display.js';
|
||||||
import { Button } from '$lib/ui/core/button/index.js';
|
import { Button } from '@evobgp/ui/components/button/index.js';
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
CardDescription
|
CardDescription
|
||||||
} from '$lib/ui/core/card/index.js';
|
} from '@evobgp/ui/components/card/index.js';
|
||||||
import AppDataTable from '$lib/ui/patterns/data-table/app-data-table.svelte';
|
import AppDataTable from '$lib/components/patterns/data-table/app-data-table.svelte';
|
||||||
import ArrowRight from '@lucide/svelte/icons/arrow-right';
|
import ArrowRight from '@lucide/svelte/icons/arrow-right';
|
||||||
import ExternalLink from '@lucide/svelte/icons/external-link';
|
import ExternalLink from '@lucide/svelte/icons/external-link';
|
||||||
|
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { Snippet } from 'svelte';
|
||||||
|
import { cn } from '$lib/utils.js';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
children: Snippet;
|
||||||
|
class?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
let { children, class: className }: Props = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class={cn('flex flex-col gap-4 md:gap-6', className)}>
|
||||||
|
{@render children()}
|
||||||
|
</div>
|
||||||
+1
-1
@@ -8,7 +8,7 @@
|
|||||||
AlertDialogFooter,
|
AlertDialogFooter,
|
||||||
AlertDialogHeader,
|
AlertDialogHeader,
|
||||||
AlertDialogTitle
|
AlertDialogTitle
|
||||||
} from '$lib/ui/core/alert-dialog/index.js';
|
} from '@evobgp/ui/components/alert-dialog/index.js';
|
||||||
import { closeConfirm, confirmState } from './confirm-state.svelte.js';
|
import { closeConfirm, confirmState } from './confirm-state.svelte.js';
|
||||||
|
|
||||||
const state = $derived(confirmState.current);
|
const state = $derived(confirmState.current);
|
||||||
+48
-6
@@ -1,14 +1,14 @@
|
|||||||
<script lang="ts" generics="T extends Record<string, unknown>">
|
<script lang="ts" generics="T extends Record<string, unknown>">
|
||||||
import type { Snippet } from 'svelte';
|
import type { Snippet } from 'svelte';
|
||||||
import { cn } from '$lib/utils.js';
|
import { cn } from '$lib/utils.js';
|
||||||
import * as Table from '$lib/ui/core/table/index.js';
|
import * as Table from '@evobgp/ui/components/table/index.js';
|
||||||
import { Skeleton } from '$lib/ui/core/skeleton/index.js';
|
import { Skeleton } from '@evobgp/ui/components/skeleton/index.js';
|
||||||
import { Alert, AlertDescription, AlertTitle } from '$lib/ui/core/alert/index.js';
|
import { Alert, AlertDescription, AlertTitle } from '@evobgp/ui/components/alert/index.js';
|
||||||
import EmptyState from '$lib/ui/patterns/empty-state/empty-state.svelte';
|
import EmptyState from '$lib/components/patterns/empty-state/empty-state.svelte';
|
||||||
import ArrowUpDown from '@lucide/svelte/icons/arrow-up-down';
|
import ArrowUpDown from '@lucide/svelte/icons/arrow-up-down';
|
||||||
import ArrowUp from '@lucide/svelte/icons/arrow-up';
|
import ArrowUp from '@lucide/svelte/icons/arrow-up';
|
||||||
import ArrowDown from '@lucide/svelte/icons/arrow-down';
|
import ArrowDown from '@lucide/svelte/icons/arrow-down';
|
||||||
import { Button } from '$lib/ui/core/button/index.js';
|
import { Button } from '@evobgp/ui/components/button/index.js';
|
||||||
import type { DataTableColumn } from './types.js';
|
import type { DataTableColumn } from './types.js';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -19,6 +19,8 @@
|
|||||||
error?: string | null;
|
error?: string | null;
|
||||||
emptyTitle?: string;
|
emptyTitle?: string;
|
||||||
emptyDescription?: string;
|
emptyDescription?: string;
|
||||||
|
/** Client-side page size; 0 = all rows */
|
||||||
|
pageSize?: number;
|
||||||
toolbar?: Snippet;
|
toolbar?: Snippet;
|
||||||
cell: Snippet<[{ row: T; column: DataTableColumn<T> }]>;
|
cell: Snippet<[{ row: T; column: DataTableColumn<T> }]>;
|
||||||
class?: string;
|
class?: string;
|
||||||
@@ -32,6 +34,7 @@
|
|||||||
error = null,
|
error = null,
|
||||||
emptyTitle = 'Нет записей',
|
emptyTitle = 'Нет записей',
|
||||||
emptyDescription,
|
emptyDescription,
|
||||||
|
pageSize = 0,
|
||||||
toolbar,
|
toolbar,
|
||||||
cell,
|
cell,
|
||||||
class: className
|
class: className
|
||||||
@@ -39,6 +42,7 @@
|
|||||||
|
|
||||||
let sortColumnId = $state<string | null>(null);
|
let sortColumnId = $state<string | null>(null);
|
||||||
let sortDir = $state<'asc' | 'desc'>('asc');
|
let sortDir = $state<'asc' | 'desc'>('asc');
|
||||||
|
let pageIndex = $state(0);
|
||||||
|
|
||||||
const sortedRows = $derived.by(() => {
|
const sortedRows = $derived.by(() => {
|
||||||
if (!sortColumnId) return rows;
|
if (!sortColumnId) return rows;
|
||||||
@@ -57,6 +61,21 @@
|
|||||||
return copy;
|
return copy;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const paginatedRows = $derived.by(() => {
|
||||||
|
if (!pageSize || pageSize <= 0) return sortedRows;
|
||||||
|
const start = pageIndex * pageSize;
|
||||||
|
return sortedRows.slice(start, start + pageSize);
|
||||||
|
});
|
||||||
|
|
||||||
|
const pageCount = $derived(
|
||||||
|
pageSize > 0 ? Math.max(1, Math.ceil(sortedRows.length / pageSize)) : 1
|
||||||
|
);
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
rows;
|
||||||
|
pageIndex = 0;
|
||||||
|
});
|
||||||
|
|
||||||
function toggleSort(col: DataTableColumn<T>) {
|
function toggleSort(col: DataTableColumn<T>) {
|
||||||
if (!col.sortable) return;
|
if (!col.sortable) return;
|
||||||
if (sortColumnId === col.id) {
|
if (sortColumnId === col.id) {
|
||||||
@@ -131,7 +150,7 @@
|
|||||||
</Table.Cell>
|
</Table.Cell>
|
||||||
</Table.Row>
|
</Table.Row>
|
||||||
{:else}
|
{:else}
|
||||||
{#each sortedRows as row (rowKey(row))}
|
{#each paginatedRows as row (rowKey(row))}
|
||||||
<Table.Row>
|
<Table.Row>
|
||||||
{#each columns as col (col.id)}
|
{#each columns as col (col.id)}
|
||||||
<Table.Cell class={col.class}>
|
<Table.Cell class={col.class}>
|
||||||
@@ -144,4 +163,27 @@
|
|||||||
</Table.Body>
|
</Table.Body>
|
||||||
</Table.Root>
|
</Table.Root>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{#if pageSize > 0 && sortedRows.length > pageSize}
|
||||||
|
<div class="flex items-center justify-between gap-2 text-sm text-muted-foreground">
|
||||||
|
<span>
|
||||||
|
{pageIndex * pageSize + 1}–{Math.min((pageIndex + 1) * pageSize, sortedRows.length)} из
|
||||||
|
{sortedRows.length}
|
||||||
|
</span>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
disabled={pageIndex === 0}
|
||||||
|
onclick={() => (pageIndex -= 1)}>Назад</Button
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
disabled={pageIndex >= pageCount - 1}
|
||||||
|
onclick={() => (pageIndex += 1)}>Вперёд</Button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { Component, Snippet } from 'svelte';
|
||||||
|
import { cn } from '$lib/utils.js';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
title?: string;
|
||||||
|
description?: string;
|
||||||
|
icon?: Component;
|
||||||
|
action?: Snippet;
|
||||||
|
class?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
let { title = 'Нет данных', description, icon: Icon, action, class: className }: Props = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class={cn('flex flex-col items-center justify-center gap-2 px-4 py-12 text-center', className)}
|
||||||
|
>
|
||||||
|
{#if Icon}
|
||||||
|
<div class="mb-1 text-muted-foreground/60" aria-hidden="true">
|
||||||
|
<Icon class="size-10" />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
<p class="text-sm font-medium">{title}</p>
|
||||||
|
{#if description}
|
||||||
|
<p class="max-w-sm text-sm text-muted-foreground">{description}</p>
|
||||||
|
{/if}
|
||||||
|
{#if action}
|
||||||
|
<div class="mt-2">{@render action()}</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Card, CardContent, CardHeader } from '$lib/ui/core/card/index.js';
|
import { Card, CardContent, CardHeader } from '@evobgp/ui/components/card/index.js';
|
||||||
import { Skeleton } from '$lib/ui/core/skeleton/index.js';
|
import { Skeleton } from '@evobgp/ui/components/skeleton/index.js';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Card>
|
<Card>
|
||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Skeleton } from '$lib/ui/core/skeleton/index.js';
|
import { Skeleton } from '@evobgp/ui/components/skeleton/index.js';
|
||||||
import * as Table from '$lib/ui/core/table/index.js';
|
import * as Table from '@evobgp/ui/components/table/index.js';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
columns?: number;
|
columns?: number;
|
||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Input } from '$lib/ui/core/input/index.js';
|
import { Input } from '@evobgp/ui/components/input/index.js';
|
||||||
import { cn } from '$lib/utils.js';
|
import { cn } from '$lib/utils.js';
|
||||||
import type { ComponentProps } from 'svelte';
|
import type { ComponentProps } from 'svelte';
|
||||||
|
|
||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Textarea } from '$lib/ui/core/textarea/index.js';
|
import { Textarea } from '@evobgp/ui/components/textarea/index.js';
|
||||||
import { cn } from '$lib/utils.js';
|
import { cn } from '$lib/utils.js';
|
||||||
import type { ComponentProps } from 'svelte';
|
import type { ComponentProps } from 'svelte';
|
||||||
|
|
||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Snippet } from 'svelte';
|
import type { Snippet } from 'svelte';
|
||||||
import { cn } from '$lib/utils.js';
|
import { cn } from '$lib/utils.js';
|
||||||
import { Label } from '$lib/ui/core/label/index.js';
|
import { Label } from '@evobgp/ui/components/label/index.js';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
label: string;
|
label: string;
|
||||||
+4
-4
@@ -1,15 +1,15 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Component } from 'svelte';
|
import type { Component } from 'svelte';
|
||||||
import { Badge } from '$lib/ui/core/badge/index.js';
|
import { Badge } from '@evobgp/ui/components/badge/index.js';
|
||||||
import { Button } from '$lib/ui/core/button/index.js';
|
import { Button } from '@evobgp/ui/components/button/index.js';
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
CardDescription
|
CardDescription
|
||||||
} from '$lib/ui/core/card/index.js';
|
} from '@evobgp/ui/components/card/index.js';
|
||||||
import CardSkeleton from '$lib/ui/patterns/feedback/card-skeleton.svelte';
|
import CardSkeleton from '$lib/components/patterns/feedback/card-skeleton.svelte';
|
||||||
import { cn } from '$lib/utils.js';
|
import { cn } from '$lib/utils.js';
|
||||||
import ArrowRight from '@lucide/svelte/icons/arrow-right';
|
import ArrowRight from '@lucide/svelte/icons/arrow-right';
|
||||||
|
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
<script lang="ts" generics="T">
|
||||||
|
import type { Snippet } from 'svelte';
|
||||||
|
import AlertCircle from '@lucide/svelte/icons/alert-circle';
|
||||||
|
import RefreshCw from '@lucide/svelte/icons/refresh-cw';
|
||||||
|
import { Button } from '@evobgp/ui/components/button/index.js';
|
||||||
|
import { Skeleton } from '@evobgp/ui/components/skeleton/index.js';
|
||||||
|
import EmptyState from '$lib/components/empty-state.svelte';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
data: T | undefined;
|
||||||
|
isLoading: boolean;
|
||||||
|
isError: boolean;
|
||||||
|
error?: unknown;
|
||||||
|
empty?: boolean;
|
||||||
|
emptyTitle?: string;
|
||||||
|
emptyDescription?: string;
|
||||||
|
emptyAction?: Snippet;
|
||||||
|
onRetry?: () => void;
|
||||||
|
skeleton?: Snippet;
|
||||||
|
children: Snippet<[T]>;
|
||||||
|
};
|
||||||
|
|
||||||
|
let {
|
||||||
|
data,
|
||||||
|
isLoading,
|
||||||
|
isError,
|
||||||
|
error,
|
||||||
|
empty = false,
|
||||||
|
emptyTitle = 'Нет данных',
|
||||||
|
emptyDescription,
|
||||||
|
emptyAction,
|
||||||
|
onRetry,
|
||||||
|
skeleton,
|
||||||
|
children
|
||||||
|
}: Props = $props();
|
||||||
|
|
||||||
|
const errorMessage = $derived(
|
||||||
|
error instanceof Error ? error.message : 'Не удалось загрузить данные'
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if isLoading}
|
||||||
|
{#if skeleton}
|
||||||
|
{@render skeleton()}
|
||||||
|
{:else}
|
||||||
|
<div class="flex flex-col gap-3">
|
||||||
|
<Skeleton class="h-8 w-48" />
|
||||||
|
<Skeleton class="h-32 w-full" />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{:else if isError}
|
||||||
|
<EmptyState
|
||||||
|
title="Ошибка загрузки"
|
||||||
|
description={errorMessage}
|
||||||
|
icon={AlertCircle}
|
||||||
|
action={onRetry ? retryAction : undefined}
|
||||||
|
/>
|
||||||
|
{:else if empty || data == null}
|
||||||
|
<EmptyState title={emptyTitle} description={emptyDescription} action={emptyAction} />
|
||||||
|
{:else}
|
||||||
|
{@render children(data)}
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#snippet retryAction()}
|
||||||
|
<Button variant="outline" size="sm" onclick={onRetry}>
|
||||||
|
<RefreshCw class="size-4" />
|
||||||
|
Повторить
|
||||||
|
</Button>
|
||||||
|
{/snippet}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Card, CardContent } from '@evobgp/ui/components/card/index.js';
|
||||||
|
import { Skeleton } from '@evobgp/ui/components/skeleton/index.js';
|
||||||
|
import { cn } from '$lib/utils.js';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
count?: number;
|
||||||
|
class?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
let { count = 4, class: className }: Props = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class={cn('grid gap-3 sm:grid-cols-2 lg:grid-cols-3', className)}>
|
||||||
|
{#each Array.from({ length: count }) as _, i (i)}
|
||||||
|
<Card>
|
||||||
|
<CardContent class="flex items-start gap-2.5 px-3 py-2.5">
|
||||||
|
<Skeleton class="size-7 shrink-0 rounded-md" />
|
||||||
|
<div class="flex flex-1 flex-col gap-2">
|
||||||
|
<Skeleton class="h-3 w-20" />
|
||||||
|
<Skeleton class="h-6 w-16" />
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { Component, Snippet } from 'svelte';
|
||||||
|
import { Card, CardContent } from '@evobgp/ui/components/card/index.js';
|
||||||
|
import { cn } from '$lib/utils.js';
|
||||||
|
|
||||||
|
export type SectionCardItem = {
|
||||||
|
label: string | Snippet;
|
||||||
|
value: string | number | Snippet;
|
||||||
|
hint?: string | Snippet;
|
||||||
|
icon?: Component;
|
||||||
|
badge?: Snippet;
|
||||||
|
variant?: 'default' | 'warning' | 'destructive';
|
||||||
|
active?: boolean;
|
||||||
|
onClick?: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
items: SectionCardItem[];
|
||||||
|
class?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
let { items, class: className }: Props = $props();
|
||||||
|
|
||||||
|
const variantClass: Record<NonNullable<SectionCardItem['variant']>, string> = {
|
||||||
|
default: '',
|
||||||
|
warning: 'border-warning/50',
|
||||||
|
destructive: 'border-destructive/50'
|
||||||
|
};
|
||||||
|
|
||||||
|
const valueVariantClass: Record<NonNullable<SectionCardItem['variant']>, string> = {
|
||||||
|
default: '',
|
||||||
|
warning: 'text-warning',
|
||||||
|
destructive: 'text-destructive'
|
||||||
|
};
|
||||||
|
|
||||||
|
function sectionGridClass(count: number): string {
|
||||||
|
if (count <= 1) return 'grid-cols-1';
|
||||||
|
if (count === 2) return 'sm:grid-cols-2';
|
||||||
|
if (count === 3) return 'sm:grid-cols-2 lg:grid-cols-3';
|
||||||
|
if (count === 4) return 'sm:grid-cols-2 lg:grid-cols-4';
|
||||||
|
if (count === 5) return 'sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-5';
|
||||||
|
if (count === 6) return 'sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-6';
|
||||||
|
return 'sm:grid-cols-2 lg:grid-cols-3';
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class={cn('grid gap-3', sectionGridClass(items.length), className)}>
|
||||||
|
{#each items as item, idx (typeof item.label === 'string' ? item.label : idx)}
|
||||||
|
{@const clickable = Boolean(item.onClick)}
|
||||||
|
<Card
|
||||||
|
class={cn(
|
||||||
|
'gap-0',
|
||||||
|
variantClass[item.variant ?? 'default'],
|
||||||
|
item.active && 'border-primary ring-1 ring-primary/30',
|
||||||
|
clickable && 'cursor-pointer transition-colors hover:bg-muted/40'
|
||||||
|
)}
|
||||||
|
onclick={item.onClick}
|
||||||
|
role={clickable ? 'button' : undefined}
|
||||||
|
tabindex={clickable ? 0 : undefined}
|
||||||
|
onkeydown={(e) => {
|
||||||
|
if (clickable && (e.key === 'Enter' || e.key === ' ')) {
|
||||||
|
e.preventDefault();
|
||||||
|
item.onClick?.();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CardContent class="flex items-start gap-2.5 px-3 py-2.5">
|
||||||
|
{#if item.icon}
|
||||||
|
<span
|
||||||
|
class="flex size-7 shrink-0 items-center justify-center rounded-md bg-muted/60 text-muted-foreground"
|
||||||
|
>
|
||||||
|
<item.icon class="size-4" />
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
|
<div class="flex min-w-0 flex-1 flex-col gap-0.5">
|
||||||
|
<div class="flex items-center justify-between gap-2">
|
||||||
|
{#if typeof item.label === 'string'}
|
||||||
|
<span class="truncate text-xs text-muted-foreground">{item.label}</span>
|
||||||
|
{:else}
|
||||||
|
<span class="truncate text-xs text-muted-foreground">{@render item.label()}</span>
|
||||||
|
{/if}
|
||||||
|
{#if item.badge}
|
||||||
|
<span class="shrink-0">{@render item.badge()}</span>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<div class="flex min-w-0 items-baseline gap-1.5">
|
||||||
|
<span
|
||||||
|
class={cn(
|
||||||
|
'flex items-center gap-1 text-lg font-semibold tabular-nums',
|
||||||
|
valueVariantClass[item.variant ?? 'default']
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{#if typeof item.value === 'string' || typeof item.value === 'number'}
|
||||||
|
{item.value}
|
||||||
|
{:else}
|
||||||
|
{@render item.value()}
|
||||||
|
{/if}
|
||||||
|
</span>
|
||||||
|
{#if item.hint}
|
||||||
|
{#if typeof item.hint === 'string'}
|
||||||
|
<span class="truncate text-xs text-muted-foreground">· {item.hint}</span>
|
||||||
|
{:else}
|
||||||
|
<span class="truncate text-xs text-muted-foreground">· {@render item.hint()}</span>
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Badge } from '@evobgp/ui/components/badge/index.js';
|
||||||
|
|
||||||
|
type BadgeVariant = 'default' | 'secondary' | 'destructive' | 'outline';
|
||||||
|
|
||||||
|
const STATUS_VARIANT: Record<string, BadgeVariant> = {
|
||||||
|
active: 'default',
|
||||||
|
ok: 'default',
|
||||||
|
enabled: 'default',
|
||||||
|
paused: 'secondary',
|
||||||
|
disabled: 'secondary',
|
||||||
|
error: 'destructive',
|
||||||
|
failed: 'destructive',
|
||||||
|
running: 'outline',
|
||||||
|
warning: 'outline',
|
||||||
|
stale: 'outline'
|
||||||
|
};
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
status: string;
|
||||||
|
label?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
let { status, label }: Props = $props();
|
||||||
|
|
||||||
|
const variant = $derived(STATUS_VARIANT[status] ?? 'outline');
|
||||||
|
const text = $derived(label ?? status);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Badge {variant}>{text}</Badge>
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Skeleton } from '@evobgp/ui/components/skeleton/index.js';
|
||||||
|
import * as Table from '@evobgp/ui/components/table/index.js';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
columns?: number;
|
||||||
|
rows?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
let { columns = 4, rows = 5 }: Props = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="rounded-md border">
|
||||||
|
<Table.Root>
|
||||||
|
<Table.Body>
|
||||||
|
{#each Array(rows) as _, ri (ri)}
|
||||||
|
<Table.Row>
|
||||||
|
{#each Array(columns) as _, ci (ci)}
|
||||||
|
<Table.Cell>
|
||||||
|
<Skeleton class="h-5 w-full max-w-[10rem]" />
|
||||||
|
</Table.Cell>
|
||||||
|
{/each}
|
||||||
|
</Table.Row>
|
||||||
|
{/each}
|
||||||
|
</Table.Body>
|
||||||
|
</Table.Root>
|
||||||
|
</div>
|
||||||
+5
-5
@@ -6,17 +6,17 @@
|
|||||||
patchSettings,
|
patchSettings,
|
||||||
type AdditionalSettingEntry
|
type AdditionalSettingEntry
|
||||||
} from '$lib/settings/settings-api.js';
|
} from '$lib/settings/settings-api.js';
|
||||||
import { Button } from '$lib/ui/core/button/index.js';
|
import { Button } from '@evobgp/ui/components/button/index.js';
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
CardDescription,
|
CardDescription,
|
||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle
|
CardTitle
|
||||||
} from '$lib/ui/core/card/index.js';
|
} from '@evobgp/ui/components/card/index.js';
|
||||||
import { Input } from '$lib/ui/core/input/index.js';
|
import { Input } from '@evobgp/ui/components/input/index.js';
|
||||||
import EmptyState from '$lib/ui/patterns/empty-state/empty-state.svelte';
|
import EmptyState from '$lib/components/patterns/empty-state/empty-state.svelte';
|
||||||
import { confirm } from '$lib/ui/patterns/confirm/confirm-state.svelte.js';
|
import { confirm } from '$lib/components/patterns/confirm/confirm-state.svelte.js';
|
||||||
import { notify, notifyApiError } from '$lib/ui/app/toast.js';
|
import { notify, notifyApiError } from '$lib/ui/app/toast.js';
|
||||||
import Save from '@lucide/svelte/icons/save';
|
import Save from '@lucide/svelte/icons/save';
|
||||||
import Plus from '@lucide/svelte/icons/plus';
|
import Plus from '@lucide/svelte/icons/plus';
|
||||||
+5
-5
@@ -14,17 +14,17 @@
|
|||||||
patchSettings
|
patchSettings
|
||||||
} from '$lib/settings/settings-api.js';
|
} from '$lib/settings/settings-api.js';
|
||||||
import { BIRD_SETTING_KEYS } from '$lib/settings/settings-known-keys.js';
|
import { BIRD_SETTING_KEYS } from '$lib/settings/settings-known-keys.js';
|
||||||
import { Button } from '$lib/ui/core/button/index.js';
|
import { Button } from '@evobgp/ui/components/button/index.js';
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
CardDescription,
|
CardDescription,
|
||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle
|
CardTitle
|
||||||
} from '$lib/ui/core/card/index.js';
|
} from '@evobgp/ui/components/card/index.js';
|
||||||
import { Input } from '$lib/ui/core/input/index.js';
|
import { Input } from '@evobgp/ui/components/input/index.js';
|
||||||
import { Alert, AlertDescription, AlertTitle } from '$lib/ui/core/alert/index.js';
|
import { Alert, AlertDescription, AlertTitle } from '@evobgp/ui/components/alert/index.js';
|
||||||
import FormField from '$lib/ui/patterns/form/form-field.svelte';
|
import FormField from '$lib/components/patterns/form/form-field.svelte';
|
||||||
import { notify, notifyApiError } from '$lib/ui/app/toast.js';
|
import { notify, notifyApiError } from '$lib/ui/app/toast.js';
|
||||||
import Save from '@lucide/svelte/icons/save';
|
import Save from '@lucide/svelte/icons/save';
|
||||||
import Info from '@lucide/svelte/icons/info';
|
import Info from '@lucide/svelte/icons/info';
|
||||||
+5
-5
@@ -21,17 +21,17 @@
|
|||||||
} from '$lib/settings/settings-api.js';
|
} from '$lib/settings/settings-api.js';
|
||||||
import { REVISION_SETTING_KEYS } from '$lib/settings/settings-known-keys.js';
|
import { REVISION_SETTING_KEYS } from '$lib/settings/settings-known-keys.js';
|
||||||
import { formatBytes } from '$lib/monitoring/postgres.js';
|
import { formatBytes } from '$lib/monitoring/postgres.js';
|
||||||
import { Button } from '$lib/ui/core/button/index.js';
|
import { Button } from '@evobgp/ui/components/button/index.js';
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
CardDescription,
|
CardDescription,
|
||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle
|
CardTitle
|
||||||
} from '$lib/ui/core/card/index.js';
|
} from '@evobgp/ui/components/card/index.js';
|
||||||
import { Input } from '$lib/ui/core/input/index.js';
|
import { Input } from '@evobgp/ui/components/input/index.js';
|
||||||
import FormField from '$lib/ui/patterns/form/form-field.svelte';
|
import FormField from '$lib/components/patterns/form/form-field.svelte';
|
||||||
import { confirm } from '$lib/ui/patterns/confirm/confirm-state.svelte.js';
|
import { confirm } from '$lib/components/patterns/confirm/confirm-state.svelte.js';
|
||||||
import { notify, notifyApiError } from '$lib/ui/app/toast.js';
|
import { notify, notifyApiError } from '$lib/ui/app/toast.js';
|
||||||
import Save from '@lucide/svelte/icons/save';
|
import Save from '@lucide/svelte/icons/save';
|
||||||
import Trash2 from '@lucide/svelte/icons/trash-2';
|
import Trash2 from '@lucide/svelte/icons/trash-2';
|
||||||
+12
-7
@@ -22,19 +22,24 @@
|
|||||||
} from '$lib/runtime-logs/runtime-logs-auto-api.js';
|
} from '$lib/runtime-logs/runtime-logs-auto-api.js';
|
||||||
import { isRuntimeLogsUnavailable } from '$lib/runtime-logs/runtime-logs-api.js';
|
import { isRuntimeLogsUnavailable } from '$lib/runtime-logs/runtime-logs-api.js';
|
||||||
import { formatBytes } from '$lib/monitoring/postgres.js';
|
import { formatBytes } from '$lib/monitoring/postgres.js';
|
||||||
import { Button } from '$lib/ui/core/button/index.js';
|
import { Button } from '@evobgp/ui/components/button/index.js';
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
CardDescription,
|
CardDescription,
|
||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle
|
CardTitle
|
||||||
} from '$lib/ui/core/card/index.js';
|
} from '@evobgp/ui/components/card/index.js';
|
||||||
import { Input } from '$lib/ui/core/input/index.js';
|
import { Input } from '@evobgp/ui/components/input/index.js';
|
||||||
import { Switch } from '$lib/ui/core/switch/index.js';
|
import { Switch } from '@evobgp/ui/components/switch/index.js';
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger } from '$lib/ui/core/select/index.js';
|
import {
|
||||||
import FormField from '$lib/ui/patterns/form/form-field.svelte';
|
Select,
|
||||||
import { confirm } from '$lib/ui/patterns/confirm/confirm-state.svelte.js';
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger
|
||||||
|
} from '@evobgp/ui/components/select/index.js';
|
||||||
|
import FormField from '$lib/components/patterns/form/form-field.svelte';
|
||||||
|
import { confirm } from '$lib/components/patterns/confirm/confirm-state.svelte.js';
|
||||||
import { notify, notifyApiError } from '$lib/ui/app/toast.js';
|
import { notify, notifyApiError } from '$lib/ui/app/toast.js';
|
||||||
import Save from '@lucide/svelte/icons/save';
|
import Save from '@lucide/svelte/icons/save';
|
||||||
import Play from '@lucide/svelte/icons/play';
|
import Play from '@lucide/svelte/icons/play';
|
||||||
+6
-5
@@ -2,9 +2,10 @@
|
|||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { page } from '$app/state';
|
import { page } from '$app/state';
|
||||||
|
import PageShell from '$lib/components/page-shell.svelte';
|
||||||
import PageHeader from '$lib/ui/app/page-header/page-header.svelte';
|
import PageHeader from '$lib/ui/app/page-header/page-header.svelte';
|
||||||
import { Alert, AlertDescription, AlertTitle } from '$lib/ui/core/alert/index.js';
|
import { Alert, AlertDescription, AlertTitle } from '@evobgp/ui/components/alert/index.js';
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '$lib/ui/core/tabs/index.js';
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@evobgp/ui/components/tabs/index.js';
|
||||||
import TenantBirdSettingsCard from '$lib/components/tenant-settings/TenantBirdSettingsCard.svelte';
|
import TenantBirdSettingsCard from '$lib/components/tenant-settings/TenantBirdSettingsCard.svelte';
|
||||||
import TenantRevisionSettingsCard from '$lib/components/tenant-settings/TenantRevisionSettingsCard.svelte';
|
import TenantRevisionSettingsCard from '$lib/components/tenant-settings/TenantRevisionSettingsCard.svelte';
|
||||||
import TenantAdditionalSettingsCard from '$lib/components/tenant-settings/TenantAdditionalSettingsCard.svelte';
|
import TenantAdditionalSettingsCard from '$lib/components/tenant-settings/TenantAdditionalSettingsCard.svelte';
|
||||||
@@ -44,7 +45,7 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex flex-col gap-6">
|
<PageShell>
|
||||||
<PageHeader
|
<PageHeader
|
||||||
title="Параметры tenant"
|
title="Параметры tenant"
|
||||||
description="Параметры control plane для текущего tenant (API /v1/settings). Токен и тема интерфейса — в разделе «Настройки»."
|
description="Параметры control plane для текущего tenant (API /v1/settings). Токен и тема интерфейса — в разделе «Настройки»."
|
||||||
@@ -62,7 +63,7 @@
|
|||||||
</Alert>
|
</Alert>
|
||||||
|
|
||||||
<Tabs bind:value={activeTab}>
|
<Tabs bind:value={activeTab}>
|
||||||
<div class="overflow-x-auto pb-1 [scrollbar-gutter:stable]">
|
<div class="[scrollbar-gutter:stable] overflow-x-auto pb-1">
|
||||||
<TabsList class="inline-flex min-w-max">
|
<TabsList class="inline-flex min-w-max">
|
||||||
<TabsTrigger value="bird">BIRD</TabsTrigger>
|
<TabsTrigger value="bird">BIRD</TabsTrigger>
|
||||||
<TabsTrigger value="revision">Ревизии</TabsTrigger>
|
<TabsTrigger value="revision">Ревизии</TabsTrigger>
|
||||||
@@ -87,4 +88,4 @@
|
|||||||
<TenantAdditionalSettingsCard />
|
<TenantAdditionalSettingsCard />
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</div>
|
</PageShell>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user