Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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",
|
||||
"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:
|
||||
- web/**
|
||||
- apps/web/**
|
||||
- packages/ui/**
|
||||
- packages/shared/**
|
||||
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`) |
|
||||
| Паттерны | `src/lib/ui/patterns/` | FormField, AppDataTable, ConfirmDialog, EmptyState |
|
||||
| App chrome | `src/lib/ui/app/` | Layout, PageHeader, `notify` |
|
||||
| Legacy | `src/lib/components/ui/` | Re-export; **не добавлять новые файлы** |
|
||||
| Примитивы | `packages/ui/src/components/` | shadcn-svelte (только CLI) |
|
||||
| Паттерны | `apps/web/src/lib/components/patterns/` | AppDataTable, FormField, ConfirmDialog |
|
||||
| Shared UI | `apps/web/src/lib/components/` | PageShell, QueryState, SectionCards |
|
||||
| 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
|
||||
npm run check
|
||||
npm run lint
|
||||
pnpm dlx shadcn-svelte@latest add <component> -y -o
|
||||
```
|
||||
Если `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.
|
||||
*Проверка:* review.
|
||||
- **WEB-01** Новый UI — сначала shadcn-svelte docs; не HTML+CSS с нуля
|
||||
- **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/`.
|
||||
*Проверка:* review.
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
|
||||
---
|
||||
```powershell
|
||||
pnpm --filter @evobgp/web check
|
||||
pnpm --filter @evobgp/web lint
|
||||
```
|
||||
|
||||
## Enforcement
|
||||
|
||||
**Обязательный финальный шаг агента при правках `web/**`:** `npm run check` **и** `npm run lint` (см. **WEB-19**). Только `check` недостаточно.
|
||||
|
||||
```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`.
|
||||
PR checklist: `check` + `lint` exit 0; примитивы в `@evobgp/ui`, не дубли в routes.
|
||||
|
||||
+11
-11
@@ -98,9 +98,9 @@ jobs:
|
||||
openapi=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
|
||||
;;
|
||||
deploy/bird/*)
|
||||
@@ -126,7 +126,7 @@ jobs:
|
||||
docs/*)
|
||||
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
|
||||
;;
|
||||
*)
|
||||
@@ -169,16 +169,16 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "20"
|
||||
cache: npm
|
||||
cache-dependency-path: web/package-lock.json
|
||||
- name: npm ci, check, lint
|
||||
node-version: "22"
|
||||
- uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10.33.2
|
||||
- name: pnpm install, check, lint
|
||||
run: |
|
||||
set -euxo pipefail
|
||||
cd web
|
||||
npm ci
|
||||
npm run check
|
||||
npm run lint
|
||||
pnpm install --frozen-lockfile
|
||||
pnpm --filter @evobgp/web check
|
||||
pnpm --filter @evobgp/web lint
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
go:
|
||||
|
||||
@@ -13,7 +13,7 @@ repos:
|
||||
hooks:
|
||||
- id: prettier-web
|
||||
name: prettier (web)
|
||||
entry: bash -c 'cd web && npx prettier --check .'
|
||||
entry: bash -c 'pnpm --filter @evobgp/web lint'
|
||||
language: system
|
||||
files: ^web/
|
||||
files: ^apps/web/
|
||||
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)** — оглавление и роли читателя.
|
||||
2. **[docs/architecture.md](docs/architecture.md)** — компоненты `cmd/`, карта `internal/`, потоки данных (одного этого файла обычно достаточно для ориентации).
|
||||
3. Задача-специфично: [docs/api.md](docs/api.md), [docs/access.md](docs/access.md), [web/README.md](web/README.md) — только если меняете API, доступ или фронт.
|
||||
|
||||
Источник правды по HTTP-контракту: **[docs/openapi.yaml](docs/openapi.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
|
||||
```
|
||||
EvoBGP/
|
||||
├── cmd/, internal/ # Go API /v1
|
||||
├── apps/web/ # SvelteKit SPA (@evobgp/web)
|
||||
├── packages/ui/ # @evobgp/ui — shadcn-svelte primitives
|
||||
├── packages/shared/ # @evobgp/shared — types, Zod contracts
|
||||
└── pnpm-workspace.yaml
|
||||
```
|
||||
|
||||
Если `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
|
||||
|
||||
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",
|
||||
"tailwind": {
|
||||
"css": "src/routes/layout.css",
|
||||
"css": "../../packages/ui/src/styles/globals.css",
|
||||
"baseColor": "neutral"
|
||||
},
|
||||
"aliases": {
|
||||
"components": "$lib/components",
|
||||
"utils": "$lib/utils",
|
||||
"ui": "$lib/ui/core",
|
||||
"ui": "@evobgp/ui/components",
|
||||
"hooks": "$lib/hooks",
|
||||
"lib": "$lib"
|
||||
},
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "web",
|
||||
"name": "@evobgp/web",
|
||||
"private": true,
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
@@ -33,6 +33,9 @@
|
||||
"vite": "^7.3.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@evobgp/shared": "workspace:*",
|
||||
"@evobgp/ui": "workspace:*",
|
||||
"@tanstack/svelte-query": "^5.90.2",
|
||||
"@tanstack/table-core": "^8.21.3",
|
||||
"bits-ui": "^2.17.2",
|
||||
"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">
|
||||
import { apiMutate } from '$lib/api/client.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 {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle
|
||||
} from '$lib/ui/core/card/index.js';
|
||||
} from '@evobgp/ui/components/card/index.js';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -16,12 +16,17 @@
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle
|
||||
} from '$lib/ui/core/dialog/index.js';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger } from '$lib/ui/core/select/index.js';
|
||||
import FormField from '$lib/ui/patterns/form/form-field.svelte';
|
||||
import AppInput from '$lib/ui/patterns/form/app-input.svelte';
|
||||
import AppDataTable from '$lib/ui/patterns/data-table/app-data-table.svelte';
|
||||
import { confirm } from '$lib/ui/patterns/confirm/confirm-state.svelte.js';
|
||||
} from '@evobgp/ui/components/dialog/index.js';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
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 Plus from '@lucide/svelte/icons/plus';
|
||||
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">
|
||||
import { apiMutate } from '$lib/api/client.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 {
|
||||
Card,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
CardDescription
|
||||
} from '$lib/ui/core/card/index.js';
|
||||
} from '@evobgp/ui/components/card/index.js';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogFooter
|
||||
} from '$lib/ui/core/dialog/index.js';
|
||||
import FormField from '$lib/ui/patterns/form/form-field.svelte';
|
||||
import AppInput from '$lib/ui/patterns/form/app-input.svelte';
|
||||
import AppDataTable from '$lib/ui/patterns/data-table/app-data-table.svelte';
|
||||
import { confirm } from '$lib/ui/patterns/confirm/confirm-state.svelte.js';
|
||||
} from '@evobgp/ui/components/dialog/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 Plus from '@lucide/svelte/icons/plus';
|
||||
import Pencil from '@lucide/svelte/icons/pencil';
|
||||
+7
-7
@@ -1,25 +1,25 @@
|
||||
<script lang="ts">
|
||||
import { apiMutate } from '$lib/api/client.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 {
|
||||
Card,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
CardDescription
|
||||
} from '$lib/ui/core/card/index.js';
|
||||
} from '@evobgp/ui/components/card/index.js';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogFooter
|
||||
} from '$lib/ui/core/dialog/index.js';
|
||||
import FormField from '$lib/ui/patterns/form/form-field.svelte';
|
||||
import AppInput from '$lib/ui/patterns/form/app-input.svelte';
|
||||
import AppDataTable from '$lib/ui/patterns/data-table/app-data-table.svelte';
|
||||
import { confirm } from '$lib/ui/patterns/confirm/confirm-state.svelte.js';
|
||||
} from '@evobgp/ui/components/dialog/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 Plus from '@lucide/svelte/icons/plus';
|
||||
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
|
||||
} from '$lib/components/modules/module-helpers.js';
|
||||
|
||||
import { Button } from '$lib/ui/core/button/index.js';
|
||||
import { Checkbox } from '$lib/ui/core/checkbox/index.js';
|
||||
import { Button } from '@evobgp/ui/components/button/index.js';
|
||||
import { Checkbox } from '@evobgp/ui/components/checkbox/index.js';
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
CardDescription
|
||||
} from '$lib/ui/core/card/index.js';
|
||||
import AppDataTable from '$lib/ui/patterns/data-table/app-data-table.svelte';
|
||||
import { confirm } from '$lib/ui/patterns/confirm/confirm-state.svelte.js';
|
||||
} from '@evobgp/ui/components/card/index.js';
|
||||
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 ModuleAsEntryDialog from '$lib/components/modules/ModuleAsEntryDialog.svelte';
|
||||
import Plus from '@lucide/svelte/icons/plus';
|
||||
+10
-5
@@ -8,9 +8,9 @@
|
||||
NONE_OPTION,
|
||||
nullableSelectValue
|
||||
} from '$lib/components/modules/module-helpers.js';
|
||||
import { Button } from '$lib/ui/core/button/index.js';
|
||||
import { Input } from '$lib/ui/core/input/index.js';
|
||||
import { Label } from '$lib/ui/core/label/index.js';
|
||||
import { Button } from '@evobgp/ui/components/button/index.js';
|
||||
import { Input } from '@evobgp/ui/components/input/index.js';
|
||||
import { Label } from '@evobgp/ui/components/label/index.js';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -18,8 +18,13 @@
|
||||
DialogTitle,
|
||||
DialogFooter,
|
||||
DialogDescription
|
||||
} from '$lib/ui/core/dialog/index.js';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger } from '$lib/ui/core/select/index.js';
|
||||
} from '@evobgp/ui/components/dialog/index.js';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger
|
||||
} from '@evobgp/ui/components/select/index.js';
|
||||
import { notify, notifyApiError } from '$lib/ui/app/toast.js';
|
||||
|
||||
type Props = {
|
||||
+10
-5
@@ -14,17 +14,22 @@
|
||||
NONE_OPTION,
|
||||
nullableSelectValue
|
||||
} from '$lib/components/modules/module-helpers.js';
|
||||
import { Button } from '$lib/ui/core/button/index.js';
|
||||
import { Input } from '$lib/ui/core/input/index.js';
|
||||
import { Label } from '$lib/ui/core/label/index.js';
|
||||
import { Button } from '@evobgp/ui/components/button/index.js';
|
||||
import { Input } from '@evobgp/ui/components/input/index.js';
|
||||
import { Label } from '@evobgp/ui/components/label/index.js';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogFooter
|
||||
} from '$lib/ui/core/dialog/index.js';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger } from '$lib/ui/core/select/index.js';
|
||||
} from '@evobgp/ui/components/dialog/index.js';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger
|
||||
} from '@evobgp/ui/components/select/index.js';
|
||||
import { notify, notifyApiError } from '$lib/ui/app/toast.js';
|
||||
|
||||
type Props = {
|
||||
+6
-6
@@ -6,18 +6,18 @@
|
||||
communityLabel,
|
||||
normalizeCdnSourceKind
|
||||
} from '$lib/components/modules/module-helpers.js';
|
||||
import { Badge } from '$lib/ui/core/badge/index.js';
|
||||
import { Button } from '$lib/ui/core/button/index.js';
|
||||
import { Checkbox } from '$lib/ui/core/checkbox/index.js';
|
||||
import { Badge } from '@evobgp/ui/components/badge/index.js';
|
||||
import { Button } from '@evobgp/ui/components/button/index.js';
|
||||
import { Checkbox } from '@evobgp/ui/components/checkbox/index.js';
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
CardDescription
|
||||
} from '$lib/ui/core/card/index.js';
|
||||
import AppDataTable from '$lib/ui/patterns/data-table/app-data-table.svelte';
|
||||
import { confirm } from '$lib/ui/patterns/confirm/confirm-state.svelte.js';
|
||||
} from '@evobgp/ui/components/card/index.js';
|
||||
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 ModuleCdnSourceDialog from '$lib/components/modules/ModuleCdnSourceDialog.svelte';
|
||||
import Plus from '@lucide/svelte/icons/plus';
|
||||
+11
-6
@@ -2,9 +2,9 @@
|
||||
import { apiMutate } from '$lib/api/client.js';
|
||||
import type { ModuleCreate } from '$lib/api/types.js';
|
||||
import { moduleTypeRu } from '$lib/ui-labels.js';
|
||||
import { Button } from '$lib/ui/core/button/index.js';
|
||||
import { Input } from '$lib/ui/core/input/index.js';
|
||||
import { Label } from '$lib/ui/core/label/index.js';
|
||||
import { Button } from '@evobgp/ui/components/button/index.js';
|
||||
import { Input } from '@evobgp/ui/components/input/index.js';
|
||||
import { Label } from '@evobgp/ui/components/label/index.js';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -12,9 +12,14 @@
|
||||
DialogTitle,
|
||||
DialogFooter,
|
||||
DialogDescription
|
||||
} from '$lib/ui/core/dialog/index.js';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger } from '$lib/ui/core/select/index.js';
|
||||
import { Switch } from '$lib/ui/core/switch/index.js';
|
||||
} from '@evobgp/ui/components/dialog/index.js';
|
||||
import {
|
||||
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';
|
||||
|
||||
type Props = {
|
||||
+2
-2
@@ -2,8 +2,8 @@
|
||||
import { resolve } from '$app/paths';
|
||||
import type { ModuleRow } from '$lib/api/types.js';
|
||||
import { moduleEnabledRu, moduleEnabledBadgeVariant, moduleTypeRu } from '$lib/ui-labels.js';
|
||||
import { Badge } from '$lib/ui/core/badge/index.js';
|
||||
import { Button } from '$lib/ui/core/button/index.js';
|
||||
import { Badge } from '@evobgp/ui/components/badge/index.js';
|
||||
import { Button } from '@evobgp/ui/components/button/index.js';
|
||||
import PageHeader from '$lib/ui/app/page-header/page-header.svelte';
|
||||
import ArrowLeft from '@lucide/svelte/icons/arrow-left';
|
||||
import Pencil from '@lucide/svelte/icons/pencil';
|
||||
+5
-5
@@ -6,17 +6,17 @@
|
||||
sanitizeFilenamePart,
|
||||
supportsCsvIO
|
||||
} from '$lib/components/modules/module-helpers.js';
|
||||
import { Button } from '$lib/ui/core/button/index.js';
|
||||
import { Checkbox } from '$lib/ui/core/checkbox/index.js';
|
||||
import { Button } from '@evobgp/ui/components/button/index.js';
|
||||
import { Checkbox } from '@evobgp/ui/components/checkbox/index.js';
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
CardDescription
|
||||
} from '$lib/ui/core/card/index.js';
|
||||
import AppDataTable from '$lib/ui/patterns/data-table/app-data-table.svelte';
|
||||
import { confirm } from '$lib/ui/patterns/confirm/confirm-state.svelte.js';
|
||||
} from '@evobgp/ui/components/card/index.js';
|
||||
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 ModuleDomainEntryDialog from '$lib/components/modules/ModuleDomainEntryDialog.svelte';
|
||||
import Plus from '@lucide/svelte/icons/plus';
|
||||
+10
-5
@@ -8,17 +8,22 @@
|
||||
NONE_OPTION,
|
||||
nullableSelectValue
|
||||
} from '$lib/components/modules/module-helpers.js';
|
||||
import { Button } from '$lib/ui/core/button/index.js';
|
||||
import { Input } from '$lib/ui/core/input/index.js';
|
||||
import { Label } from '$lib/ui/core/label/index.js';
|
||||
import { Button } from '@evobgp/ui/components/button/index.js';
|
||||
import { Input } from '@evobgp/ui/components/input/index.js';
|
||||
import { Label } from '@evobgp/ui/components/label/index.js';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogFooter
|
||||
} from '$lib/ui/core/dialog/index.js';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger } from '$lib/ui/core/select/index.js';
|
||||
} from '@evobgp/ui/components/dialog/index.js';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger
|
||||
} from '@evobgp/ui/components/select/index.js';
|
||||
import { notify, notifyApiError } from '$lib/ui/app/toast.js';
|
||||
|
||||
type Props = {
|
||||
+12
-7
@@ -16,19 +16,24 @@
|
||||
NONE_OPTION,
|
||||
nullableSelectValue
|
||||
} from '$lib/components/modules/module-helpers.js';
|
||||
import { Button } from '$lib/ui/core/button/index.js';
|
||||
import { Input } from '$lib/ui/core/input/index.js';
|
||||
import { Label } from '$lib/ui/core/label/index.js';
|
||||
import { Checkbox } from '$lib/ui/core/checkbox/index.js';
|
||||
import { Button } from '@evobgp/ui/components/button/index.js';
|
||||
import { Input } from '@evobgp/ui/components/input/index.js';
|
||||
import { Label } from '@evobgp/ui/components/label/index.js';
|
||||
import { Checkbox } from '@evobgp/ui/components/checkbox/index.js';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogFooter
|
||||
} from '$lib/ui/core/dialog/index.js';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger } from '$lib/ui/core/select/index.js';
|
||||
import { Switch } from '$lib/ui/core/switch/index.js';
|
||||
} from '@evobgp/ui/components/dialog/index.js';
|
||||
import {
|
||||
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';
|
||||
|
||||
const dohPolicyOptions: { value: DohResolverPolicy; label: string; hint: string }[] = [
|
||||
+10
-5
@@ -2,17 +2,22 @@
|
||||
import { apiMutate } from '$lib/api/client.js';
|
||||
import type { BgpCommunity, IpRangeEntry, IpRangeEntryCreate } from '$lib/api/types.js';
|
||||
import { communityLabel, communityOptionLabel } from '$lib/components/modules/module-helpers.js';
|
||||
import { Button } from '$lib/ui/core/button/index.js';
|
||||
import { Input } from '$lib/ui/core/input/index.js';
|
||||
import { Label } from '$lib/ui/core/label/index.js';
|
||||
import { Button } from '@evobgp/ui/components/button/index.js';
|
||||
import { Input } from '@evobgp/ui/components/input/index.js';
|
||||
import { Label } from '@evobgp/ui/components/label/index.js';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogFooter
|
||||
} from '$lib/ui/core/dialog/index.js';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger } from '$lib/ui/core/select/index.js';
|
||||
} from '@evobgp/ui/components/dialog/index.js';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger
|
||||
} from '@evobgp/ui/components/select/index.js';
|
||||
import { notify, notifyApiError } from '$lib/ui/app/toast.js';
|
||||
|
||||
type Props = {
|
||||
+5
-5
@@ -6,17 +6,17 @@
|
||||
sanitizeFilenamePart,
|
||||
supportsCsvIO
|
||||
} from '$lib/components/modules/module-helpers.js';
|
||||
import { Button } from '$lib/ui/core/button/index.js';
|
||||
import { Checkbox } from '$lib/ui/core/checkbox/index.js';
|
||||
import { Button } from '@evobgp/ui/components/button/index.js';
|
||||
import { Checkbox } from '@evobgp/ui/components/checkbox/index.js';
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
CardDescription
|
||||
} from '$lib/ui/core/card/index.js';
|
||||
import AppDataTable from '$lib/ui/patterns/data-table/app-data-table.svelte';
|
||||
import { confirm } from '$lib/ui/patterns/confirm/confirm-state.svelte.js';
|
||||
} from '@evobgp/ui/components/card/index.js';
|
||||
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 ModuleIpRangeEntryDialog from '$lib/components/modules/ModuleIpRangeEntryDialog.svelte';
|
||||
import Plus from '@lucide/svelte/icons/plus';
|
||||
+2
-2
@@ -7,8 +7,8 @@
|
||||
dohProfileLabel,
|
||||
moduleDohProfileIds
|
||||
} from '$lib/components/modules/module-helpers.js';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '$lib/ui/core/card/index.js';
|
||||
import CardSkeleton from '$lib/ui/patterns/feedback/card-skeleton.svelte';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@evobgp/ui/components/card/index.js';
|
||||
import CardSkeleton from '$lib/components/patterns/feedback/card-skeleton.svelte';
|
||||
import { cn } from '$lib/utils.js';
|
||||
import ArrowDownUp from '@lucide/svelte/icons/arrow-down-up';
|
||||
import Timer from '@lucide/svelte/icons/timer';
|
||||
+2
-2
@@ -1,14 +1,14 @@
|
||||
<script lang="ts">
|
||||
import { resolve } from '$app/paths';
|
||||
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 {
|
||||
Card,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
CardDescription
|
||||
} from '$lib/ui/core/card/index.js';
|
||||
} from '@evobgp/ui/components/card/index.js';
|
||||
|
||||
type Props = {
|
||||
mod: ModuleRow;
|
||||
+13
-13
@@ -35,31 +35,31 @@
|
||||
type ScheduleEditor,
|
||||
type ScheduleMode
|
||||
} 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 {
|
||||
Card,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
CardDescription
|
||||
} from '$lib/ui/core/card/index.js';
|
||||
import { Badge } from '$lib/ui/core/badge/index.js';
|
||||
import { Alert, AlertDescription, AlertTitle } from '$lib/ui/core/alert/index.js';
|
||||
} from '@evobgp/ui/components/card/index.js';
|
||||
import { Badge } from '@evobgp/ui/components/badge/index.js';
|
||||
import { Alert, AlertDescription, AlertTitle } from '@evobgp/ui/components/alert/index.js';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogFooter
|
||||
} from '$lib/ui/core/dialog/index.js';
|
||||
import { Switch } from '$lib/ui/core/switch/index.js';
|
||||
import { Label } from '$lib/ui/core/label/index.js';
|
||||
import { Checkbox } from '$lib/ui/core/checkbox/index.js';
|
||||
import FormField from '$lib/ui/patterns/form/form-field.svelte';
|
||||
import AppInput from '$lib/ui/patterns/form/app-input.svelte';
|
||||
import AppDataTable from '$lib/ui/patterns/data-table/app-data-table.svelte';
|
||||
import type { DataTableColumn } from '$lib/ui/patterns/data-table/types.js';
|
||||
import { confirm } from '$lib/ui/patterns/confirm/confirm-state.svelte.js';
|
||||
} from '@evobgp/ui/components/dialog/index.js';
|
||||
import { Switch } from '@evobgp/ui/components/switch/index.js';
|
||||
import { Label } from '@evobgp/ui/components/label/index.js';
|
||||
import { Checkbox } from '@evobgp/ui/components/checkbox/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 type { DataTableColumn } from '$lib/components/patterns/data-table/types.js';
|
||||
import { confirm } from '$lib/components/patterns/confirm/confirm-state.svelte.js';
|
||||
import { notify, notifyApiError } from '$lib/ui/app/toast.js';
|
||||
import Plus from '@lucide/svelte/icons/plus';
|
||||
import Pencil from '@lucide/svelte/icons/pencil';
|
||||
+8
-8
@@ -17,17 +17,17 @@
|
||||
type PostgresMaintLog,
|
||||
type CorrelationResponse
|
||||
} 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 {
|
||||
Card,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
CardDescription
|
||||
} from '$lib/ui/core/card/index.js';
|
||||
import { Button } from '$lib/ui/core/button/index.js';
|
||||
import { Badge } from '$lib/ui/core/badge/index.js';
|
||||
import { Alert, AlertDescription, AlertTitle } from '$lib/ui/core/alert/index.js';
|
||||
} from '@evobgp/ui/components/card/index.js';
|
||||
import { Button } from '@evobgp/ui/components/button/index.js';
|
||||
import { Badge } from '@evobgp/ui/components/badge/index.js';
|
||||
import { Alert, AlertDescription, AlertTitle } from '@evobgp/ui/components/alert/index.js';
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
@@ -35,9 +35,9 @@
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow
|
||||
} from '$lib/ui/core/table/index.js';
|
||||
import { Switch } from '$lib/ui/core/switch/index.js';
|
||||
import { Label } from '$lib/ui/core/label/index.js';
|
||||
} from '@evobgp/ui/components/table/index.js';
|
||||
import { Switch } from '@evobgp/ui/components/switch/index.js';
|
||||
import { Label } from '@evobgp/ui/components/label/index.js';
|
||||
import Database from '@lucide/svelte/icons/database';
|
||||
import RefreshCw from '@lucide/svelte/icons/refresh-cw';
|
||||
|
||||
+10
-10
@@ -14,33 +14,33 @@
|
||||
} from '$lib/runtime-logs/runtime-logs-api.js';
|
||||
import { formatBytes } from '$lib/monitoring/postgres.js';
|
||||
import { formatDateTime } from '$lib/modules/display.js';
|
||||
import { Button } from '$lib/ui/core/button/index.js';
|
||||
import { Badge } from '$lib/ui/core/badge/index.js';
|
||||
import { Button } from '@evobgp/ui/components/button/index.js';
|
||||
import { Badge } from '@evobgp/ui/components/badge/index.js';
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle
|
||||
} from '$lib/ui/core/card/index.js';
|
||||
} from '@evobgp/ui/components/card/index.js';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle
|
||||
} from '$lib/ui/core/dialog/index.js';
|
||||
} from '@evobgp/ui/components/dialog/index.js';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger
|
||||
} from '$lib/ui/core/dropdown-menu/index.js';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '$lib/ui/core/tabs/index.js';
|
||||
import AppDataTable from '$lib/ui/patterns/data-table/app-data-table.svelte';
|
||||
import type { DataTableColumn } from '$lib/ui/patterns/data-table/types.js';
|
||||
import EmptyState from '$lib/ui/patterns/empty-state/empty-state.svelte';
|
||||
import { confirm } from '$lib/ui/patterns/confirm/confirm-state.svelte.js';
|
||||
} from '@evobgp/ui/components/dropdown-menu/index.js';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@evobgp/ui/components/tabs/index.js';
|
||||
import AppDataTable from '$lib/components/patterns/data-table/app-data-table.svelte';
|
||||
import type { DataTableColumn } from '$lib/components/patterns/data-table/types.js';
|
||||
import EmptyState from '$lib/components/patterns/empty-state/empty-state.svelte';
|
||||
import { confirm } from '$lib/components/patterns/confirm/confirm-state.svelte.js';
|
||||
import ScrollPreBlock from '$lib/components/app/scroll-pre-block.svelte';
|
||||
import { notify, notifyApiError } from '$lib/ui/app/toast.js';
|
||||
import {
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { Label } from '$lib/ui/core/label/index.js';
|
||||
import { Switch } from '$lib/ui/core/switch/index.js';
|
||||
import { Label } from '@evobgp/ui/components/label/index.js';
|
||||
import { Switch } from '@evobgp/ui/components/switch/index.js';
|
||||
import { readNetworkAutoRefresh, writeNetworkAutoRefresh } from '$lib/network/network-metrics.js';
|
||||
|
||||
type Props = {
|
||||
+2
-2
@@ -3,14 +3,14 @@
|
||||
import { resolve } from '$app/paths';
|
||||
import { loadSettings, partitionSettings } from '$lib/settings/settings-api.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 {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle
|
||||
} from '$lib/ui/core/card/index.js';
|
||||
} from '@evobgp/ui/components/card/index.js';
|
||||
import { notifyApiError } from '$lib/ui/app/toast.js';
|
||||
import ArrowRight from '@lucide/svelte/icons/arrow-right';
|
||||
import SlidersHorizontal from '@lucide/svelte/icons/sliders-horizontal';
|
||||
+3
-3
@@ -8,9 +8,9 @@
|
||||
networkOverallStatusHint,
|
||||
networkOverallStatusLabel
|
||||
} from '$lib/network/network-metrics.js';
|
||||
import { Alert, AlertDescription, AlertTitle } from '$lib/ui/core/alert/index.js';
|
||||
import { Button } from '$lib/ui/core/button/index.js';
|
||||
import KpiMetricsGrid from '$lib/ui/patterns/kpi/kpi-metrics-grid.svelte';
|
||||
import { Alert, AlertDescription, AlertTitle } from '@evobgp/ui/components/alert/index.js';
|
||||
import { Button } from '@evobgp/ui/components/button/index.js';
|
||||
import KpiMetricsGrid from '$lib/components/patterns/kpi/kpi-metrics-grid.svelte';
|
||||
import NetworkSpeakerStatusCard from '$lib/components/network/NetworkSpeakerStatusCard.svelte';
|
||||
import CheckCircle from '@lucide/svelte/icons/check-circle';
|
||||
import AlertTriangle from '@lucide/svelte/icons/alert-triangle';
|
||||
+16
-11
@@ -1,16 +1,16 @@
|
||||
<script lang="ts">
|
||||
import { apiMutate } from '$lib/api/client.js';
|
||||
import type { PeerRow, BgpPeerCreate, SpeakerRow, PeerSessionOnSpeaker } from '$lib/api/types.js';
|
||||
import { Badge } from '$lib/ui/core/badge/index.js';
|
||||
import { Button } from '$lib/ui/core/button/index.js';
|
||||
import { Label } from '$lib/ui/core/label/index.js';
|
||||
import { Badge } from '@evobgp/ui/components/badge/index.js';
|
||||
import { Button } from '@evobgp/ui/components/button/index.js';
|
||||
import { Label } from '@evobgp/ui/components/label/index.js';
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
CardDescription
|
||||
} from '$lib/ui/core/card/index.js';
|
||||
} from '@evobgp/ui/components/card/index.js';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -18,13 +18,18 @@
|
||||
DialogTitle,
|
||||
DialogFooter,
|
||||
DialogDescription
|
||||
} from '$lib/ui/core/dialog/index.js';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger } from '$lib/ui/core/select/index.js';
|
||||
import { Switch } from '$lib/ui/core/switch/index.js';
|
||||
import FormField from '$lib/ui/patterns/form/form-field.svelte';
|
||||
import AppInput from '$lib/ui/patterns/form/app-input.svelte';
|
||||
import AppDataTable from '$lib/ui/patterns/data-table/app-data-table.svelte';
|
||||
import { confirm } from '$lib/ui/patterns/confirm/confirm-state.svelte.js';
|
||||
} from '@evobgp/ui/components/dialog/index.js';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger
|
||||
} 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 Plus from '@lucide/svelte/icons/plus';
|
||||
import Pencil from '@lucide/svelte/icons/pencil';
|
||||
+6
-6
@@ -9,17 +9,17 @@
|
||||
speakerLiveAgentError,
|
||||
speakerLiveBgpError
|
||||
} from '$lib/network/network-metrics.js';
|
||||
import { Alert, AlertDescription, AlertTitle } from '$lib/ui/core/alert/index.js';
|
||||
import { Badge } from '$lib/ui/core/badge/index.js';
|
||||
import { Button } from '$lib/ui/core/button/index.js';
|
||||
import { Separator } from '$lib/ui/core/separator/index.js';
|
||||
import { Alert, AlertDescription, AlertTitle } from '@evobgp/ui/components/alert/index.js';
|
||||
import { Badge } from '@evobgp/ui/components/badge/index.js';
|
||||
import { Button } from '@evobgp/ui/components/button/index.js';
|
||||
import { Separator } from '@evobgp/ui/components/separator/index.js';
|
||||
import {
|
||||
Sheet,
|
||||
SheetContent,
|
||||
SheetDescription,
|
||||
SheetHeader,
|
||||
SheetTitle
|
||||
} from '$lib/ui/core/sheet/index.js';
|
||||
} from '@evobgp/ui/components/sheet/index.js';
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
@@ -27,7 +27,7 @@
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow
|
||||
} from '$lib/ui/core/table/index.js';
|
||||
} from '@evobgp/ui/components/table/index.js';
|
||||
import AlertTriangle from '@lucide/svelte/icons/alert-triangle';
|
||||
|
||||
type Props = {
|
||||
+2
-2
@@ -6,14 +6,14 @@
|
||||
speakerHasDrift,
|
||||
speakerLabel
|
||||
} 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 {
|
||||
Card,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
CardDescription
|
||||
} from '$lib/ui/core/card/index.js';
|
||||
} from '@evobgp/ui/components/card/index.js';
|
||||
import { cn } from '$lib/utils.js';
|
||||
import Server from '@lucide/svelte/icons/server';
|
||||
|
||||
+9
-9
@@ -6,15 +6,15 @@
|
||||
speakerDisplayStatus,
|
||||
speakerHasDrift
|
||||
} from '$lib/network/network-metrics.js';
|
||||
import { Badge } from '$lib/ui/core/badge/index.js';
|
||||
import { Button } from '$lib/ui/core/button/index.js';
|
||||
import { Badge } from '@evobgp/ui/components/badge/index.js';
|
||||
import { Button } from '@evobgp/ui/components/button/index.js';
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
CardDescription
|
||||
} from '$lib/ui/core/card/index.js';
|
||||
} from '@evobgp/ui/components/card/index.js';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -22,12 +22,12 @@
|
||||
DialogTitle,
|
||||
DialogFooter,
|
||||
DialogDescription
|
||||
} from '$lib/ui/core/dialog/index.js';
|
||||
import { Checkbox } from '$lib/ui/core/checkbox/index.js';
|
||||
import FormField from '$lib/ui/patterns/form/form-field.svelte';
|
||||
import AppInput from '$lib/ui/patterns/form/app-input.svelte';
|
||||
import AppDataTable from '$lib/ui/patterns/data-table/app-data-table.svelte';
|
||||
import { confirm } from '$lib/ui/patterns/confirm/confirm-state.svelte.js';
|
||||
} from '@evobgp/ui/components/dialog/index.js';
|
||||
import { Checkbox } from '@evobgp/ui/components/checkbox/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 Plus from '@lucide/svelte/icons/plus';
|
||||
import Pencil from '@lucide/svelte/icons/pencil';
|
||||
+8
-3
@@ -1,14 +1,19 @@
|
||||
<script lang="ts">
|
||||
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 {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle
|
||||
} from '$lib/ui/core/card/index.js';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger } from '$lib/ui/core/select/index.js';
|
||||
} from '@evobgp/ui/components/card/index.js';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger
|
||||
} from '@evobgp/ui/components/select/index.js';
|
||||
import { formatDateTime } from '$lib/modules/display.js';
|
||||
import { sortRevisionDiffItems } from '$lib/sort-prefixes.js';
|
||||
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { Button } from '$lib/ui/core/button/index.js';
|
||||
import { Input } from '$lib/ui/core/input/index.js';
|
||||
import { Label } from '$lib/ui/core/label/index.js';
|
||||
import { Button } from '@evobgp/ui/components/button/index.js';
|
||||
import { Input } from '@evobgp/ui/components/input/index.js';
|
||||
import { Label } from '@evobgp/ui/components/label/index.js';
|
||||
import Filter from '@lucide/svelte/icons/filter';
|
||||
import Search from '@lucide/svelte/icons/search';
|
||||
import X from '@lucide/svelte/icons/x';
|
||||
+4
-4
@@ -2,16 +2,16 @@
|
||||
import { SvelteMap, SvelteSet } from 'svelte/reactivity';
|
||||
import type { JobRow } from '$lib/api/types.js';
|
||||
import type { JobDetailedReport, JobLogEntry } from './types.js';
|
||||
import { Badge } from '$lib/ui/core/badge/index.js';
|
||||
import { Button } from '$lib/ui/core/button/index.js';
|
||||
import { Badge } from '@evobgp/ui/components/badge/index.js';
|
||||
import { Button } from '@evobgp/ui/components/button/index.js';
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle
|
||||
} from '$lib/ui/core/card/index.js';
|
||||
import EmptyState from '$lib/ui/patterns/empty-state/empty-state.svelte';
|
||||
} from '@evobgp/ui/components/card/index.js';
|
||||
import EmptyState from '$lib/components/patterns/empty-state/empty-state.svelte';
|
||||
import { formatDateTime } from '$lib/modules/display.js';
|
||||
import RefreshCw from '@lucide/svelte/icons/refresh-cw';
|
||||
import Eye from '@lucide/svelte/icons/eye';
|
||||
+3
-3
@@ -1,8 +1,8 @@
|
||||
<script lang="ts">
|
||||
import type { BirdStatus } from '$lib/api/types.js';
|
||||
import { Badge } from '$lib/ui/core/badge/index.js';
|
||||
import { Button } from '$lib/ui/core/button/index.js';
|
||||
import { Card } from '$lib/ui/core/card/index.js';
|
||||
import { Badge } from '@evobgp/ui/components/badge/index.js';
|
||||
import { Button } from '@evobgp/ui/components/button/index.js';
|
||||
import { Card } from '@evobgp/ui/components/card/index.js';
|
||||
import RefreshCw from '@lucide/svelte/icons/refresh-cw';
|
||||
import Play from '@lucide/svelte/icons/play';
|
||||
import RotateCcw from '@lucide/svelte/icons/rotate-ccw';
|
||||
+3
-3
@@ -1,14 +1,14 @@
|
||||
<script lang="ts">
|
||||
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 {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle
|
||||
} from '$lib/ui/core/card/index.js';
|
||||
import AppDataTable from '$lib/ui/patterns/data-table/app-data-table.svelte';
|
||||
} from '@evobgp/ui/components/card/index.js';
|
||||
import AppDataTable from '$lib/components/patterns/data-table/app-data-table.svelte';
|
||||
import { formatDateTime } from '$lib/modules/display.js';
|
||||
import RefreshCw from '@lucide/svelte/icons/refresh-cw';
|
||||
import Undo from '@lucide/svelte/icons/undo';
|
||||
+3
-3
@@ -6,9 +6,9 @@
|
||||
getCoreRowModel,
|
||||
getPaginationRowModel
|
||||
} from '@tanstack/table-core';
|
||||
import { createSvelteTable, FlexRender } from '$lib/ui/core/data-table/index.js';
|
||||
import * as Table from '$lib/ui/core/table/index.js';
|
||||
import { Button } from '$lib/ui/core/button/index.js';
|
||||
import { createSvelteTable, FlexRender } from '@evobgp/ui/components/data-table/index.js';
|
||||
import * as Table from '@evobgp/ui/components/table/index.js';
|
||||
import { Button } from '@evobgp/ui/components/button/index.js';
|
||||
|
||||
type Props = {
|
||||
rows: RowData[];
|
||||
+3
-3
@@ -8,15 +8,15 @@
|
||||
networkOverallStatusHint,
|
||||
networkOverallStatusLabel
|
||||
} from '$lib/network/network-metrics.js';
|
||||
import { Alert, AlertDescription, AlertTitle } from '$lib/ui/core/alert/index.js';
|
||||
import { Button } from '$lib/ui/core/button/index.js';
|
||||
import { Alert, AlertDescription, AlertTitle } from '@evobgp/ui/components/alert/index.js';
|
||||
import { Button } from '@evobgp/ui/components/button/index.js';
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
CardDescription
|
||||
} from '$lib/ui/core/card/index.js';
|
||||
} from '@evobgp/ui/components/card/index.js';
|
||||
import CheckCircle from '@lucide/svelte/icons/check-circle';
|
||||
import AlertTriangle from '@lucide/svelte/icons/alert-triangle';
|
||||
import XCircle from '@lucide/svelte/icons/x-circle';
|
||||
+4
-4
@@ -4,16 +4,16 @@
|
||||
import { formatDateTime } from '$lib/modules/display.js';
|
||||
import { jobKindTitle } from '$lib/operations/job-kind-label.js';
|
||||
import { jobStatusRu, jobStatusBadgeVariant } from '$lib/ui-labels.js';
|
||||
import { Badge } from '$lib/ui/core/badge/index.js';
|
||||
import { Button } from '$lib/ui/core/button/index.js';
|
||||
import { Badge } from '@evobgp/ui/components/badge/index.js';
|
||||
import { Button } from '@evobgp/ui/components/button/index.js';
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
CardDescription
|
||||
} from '$lib/ui/core/card/index.js';
|
||||
import AppDataTable from '$lib/ui/patterns/data-table/app-data-table.svelte';
|
||||
} from '@evobgp/ui/components/card/index.js';
|
||||
import AppDataTable from '$lib/components/patterns/data-table/app-data-table.svelte';
|
||||
import ArrowRight from '@lucide/svelte/icons/arrow-right';
|
||||
import ExternalLink from '@lucide/svelte/icons/external-link';
|
||||
|
||||
+3
-3
@@ -2,15 +2,15 @@
|
||||
import { resolve } from '$app/paths';
|
||||
import type { RevisionRow } from '$lib/api/types.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 {
|
||||
Card,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
CardDescription
|
||||
} from '$lib/ui/core/card/index.js';
|
||||
import AppDataTable from '$lib/ui/patterns/data-table/app-data-table.svelte';
|
||||
} from '@evobgp/ui/components/card/index.js';
|
||||
import AppDataTable from '$lib/components/patterns/data-table/app-data-table.svelte';
|
||||
import ArrowRight from '@lucide/svelte/icons/arrow-right';
|
||||
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,
|
||||
AlertDialogHeader,
|
||||
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';
|
||||
|
||||
const state = $derived(confirmState.current);
|
||||
+48
-6
@@ -1,14 +1,14 @@
|
||||
<script lang="ts" generics="T extends Record<string, unknown>">
|
||||
import type { Snippet } from 'svelte';
|
||||
import { cn } from '$lib/utils.js';
|
||||
import * as Table from '$lib/ui/core/table/index.js';
|
||||
import { Skeleton } from '$lib/ui/core/skeleton/index.js';
|
||||
import { Alert, AlertDescription, AlertTitle } from '$lib/ui/core/alert/index.js';
|
||||
import EmptyState from '$lib/ui/patterns/empty-state/empty-state.svelte';
|
||||
import * as Table from '@evobgp/ui/components/table/index.js';
|
||||
import { Skeleton } from '@evobgp/ui/components/skeleton/index.js';
|
||||
import { Alert, AlertDescription, AlertTitle } from '@evobgp/ui/components/alert/index.js';
|
||||
import EmptyState from '$lib/components/patterns/empty-state/empty-state.svelte';
|
||||
import ArrowUpDown from '@lucide/svelte/icons/arrow-up-down';
|
||||
import ArrowUp from '@lucide/svelte/icons/arrow-up';
|
||||
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';
|
||||
|
||||
type Props = {
|
||||
@@ -19,6 +19,8 @@
|
||||
error?: string | null;
|
||||
emptyTitle?: string;
|
||||
emptyDescription?: string;
|
||||
/** Client-side page size; 0 = all rows */
|
||||
pageSize?: number;
|
||||
toolbar?: Snippet;
|
||||
cell: Snippet<[{ row: T; column: DataTableColumn<T> }]>;
|
||||
class?: string;
|
||||
@@ -32,6 +34,7 @@
|
||||
error = null,
|
||||
emptyTitle = 'Нет записей',
|
||||
emptyDescription,
|
||||
pageSize = 0,
|
||||
toolbar,
|
||||
cell,
|
||||
class: className
|
||||
@@ -39,6 +42,7 @@
|
||||
|
||||
let sortColumnId = $state<string | null>(null);
|
||||
let sortDir = $state<'asc' | 'desc'>('asc');
|
||||
let pageIndex = $state(0);
|
||||
|
||||
const sortedRows = $derived.by(() => {
|
||||
if (!sortColumnId) return rows;
|
||||
@@ -57,6 +61,21 @@
|
||||
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>) {
|
||||
if (!col.sortable) return;
|
||||
if (sortColumnId === col.id) {
|
||||
@@ -131,7 +150,7 @@
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{:else}
|
||||
{#each sortedRows as row (rowKey(row))}
|
||||
{#each paginatedRows as row (rowKey(row))}
|
||||
<Table.Row>
|
||||
{#each columns as col (col.id)}
|
||||
<Table.Cell class={col.class}>
|
||||
@@ -144,4 +163,27 @@
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</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>
|
||||
@@ -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">
|
||||
import { Card, CardContent, CardHeader } from '$lib/ui/core/card/index.js';
|
||||
import { Skeleton } from '$lib/ui/core/skeleton/index.js';
|
||||
import { Card, CardContent, CardHeader } from '@evobgp/ui/components/card/index.js';
|
||||
import { Skeleton } from '@evobgp/ui/components/skeleton/index.js';
|
||||
</script>
|
||||
|
||||
<Card>
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { Skeleton } from '$lib/ui/core/skeleton/index.js';
|
||||
import * as Table from '$lib/ui/core/table/index.js';
|
||||
import { Skeleton } from '@evobgp/ui/components/skeleton/index.js';
|
||||
import * as Table from '@evobgp/ui/components/table/index.js';
|
||||
|
||||
type Props = {
|
||||
columns?: number;
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
<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 type { ComponentProps } from 'svelte';
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
<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 type { ComponentProps } from 'svelte';
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import type { Snippet } from 'svelte';
|
||||
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 = {
|
||||
label: string;
|
||||
+4
-4
@@ -1,15 +1,15 @@
|
||||
<script lang="ts">
|
||||
import type { Component } from 'svelte';
|
||||
import { Badge } from '$lib/ui/core/badge/index.js';
|
||||
import { Button } from '$lib/ui/core/button/index.js';
|
||||
import { Badge } from '@evobgp/ui/components/badge/index.js';
|
||||
import { Button } from '@evobgp/ui/components/button/index.js';
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
CardDescription
|
||||
} from '$lib/ui/core/card/index.js';
|
||||
import CardSkeleton from '$lib/ui/patterns/feedback/card-skeleton.svelte';
|
||||
} from '@evobgp/ui/components/card/index.js';
|
||||
import CardSkeleton from '$lib/components/patterns/feedback/card-skeleton.svelte';
|
||||
import { cn } from '$lib/utils.js';
|
||||
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,
|
||||
type AdditionalSettingEntry
|
||||
} 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 {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle
|
||||
} from '$lib/ui/core/card/index.js';
|
||||
import { Input } from '$lib/ui/core/input/index.js';
|
||||
import EmptyState from '$lib/ui/patterns/empty-state/empty-state.svelte';
|
||||
import { confirm } from '$lib/ui/patterns/confirm/confirm-state.svelte.js';
|
||||
} from '@evobgp/ui/components/card/index.js';
|
||||
import { Input } from '@evobgp/ui/components/input/index.js';
|
||||
import EmptyState from '$lib/components/patterns/empty-state/empty-state.svelte';
|
||||
import { confirm } from '$lib/components/patterns/confirm/confirm-state.svelte.js';
|
||||
import { notify, notifyApiError } from '$lib/ui/app/toast.js';
|
||||
import Save from '@lucide/svelte/icons/save';
|
||||
import Plus from '@lucide/svelte/icons/plus';
|
||||
+5
-5
@@ -14,17 +14,17 @@
|
||||
patchSettings
|
||||
} from '$lib/settings/settings-api.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 {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle
|
||||
} from '$lib/ui/core/card/index.js';
|
||||
import { Input } from '$lib/ui/core/input/index.js';
|
||||
import { Alert, AlertDescription, AlertTitle } from '$lib/ui/core/alert/index.js';
|
||||
import FormField from '$lib/ui/patterns/form/form-field.svelte';
|
||||
} from '@evobgp/ui/components/card/index.js';
|
||||
import { Input } from '@evobgp/ui/components/input/index.js';
|
||||
import { Alert, AlertDescription, AlertTitle } from '@evobgp/ui/components/alert/index.js';
|
||||
import FormField from '$lib/components/patterns/form/form-field.svelte';
|
||||
import { notify, notifyApiError } from '$lib/ui/app/toast.js';
|
||||
import Save from '@lucide/svelte/icons/save';
|
||||
import Info from '@lucide/svelte/icons/info';
|
||||
+5
-5
@@ -21,17 +21,17 @@
|
||||
} from '$lib/settings/settings-api.js';
|
||||
import { REVISION_SETTING_KEYS } from '$lib/settings/settings-known-keys.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 {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle
|
||||
} from '$lib/ui/core/card/index.js';
|
||||
import { Input } from '$lib/ui/core/input/index.js';
|
||||
import FormField from '$lib/ui/patterns/form/form-field.svelte';
|
||||
import { confirm } from '$lib/ui/patterns/confirm/confirm-state.svelte.js';
|
||||
} from '@evobgp/ui/components/card/index.js';
|
||||
import { Input } from '@evobgp/ui/components/input/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 Save from '@lucide/svelte/icons/save';
|
||||
import Trash2 from '@lucide/svelte/icons/trash-2';
|
||||
+12
-7
@@ -22,19 +22,24 @@
|
||||
} from '$lib/runtime-logs/runtime-logs-auto-api.js';
|
||||
import { isRuntimeLogsUnavailable } from '$lib/runtime-logs/runtime-logs-api.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 {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle
|
||||
} from '$lib/ui/core/card/index.js';
|
||||
import { Input } from '$lib/ui/core/input/index.js';
|
||||
import { Switch } from '$lib/ui/core/switch/index.js';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger } from '$lib/ui/core/select/index.js';
|
||||
import FormField from '$lib/ui/patterns/form/form-field.svelte';
|
||||
import { confirm } from '$lib/ui/patterns/confirm/confirm-state.svelte.js';
|
||||
} from '@evobgp/ui/components/card/index.js';
|
||||
import { Input } from '@evobgp/ui/components/input/index.js';
|
||||
import { Switch } from '@evobgp/ui/components/switch/index.js';
|
||||
import {
|
||||
Select,
|
||||
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 Save from '@lucide/svelte/icons/save';
|
||||
import Play from '@lucide/svelte/icons/play';
|
||||
+6
-5
@@ -2,9 +2,10 @@
|
||||
import { onMount } from 'svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
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 { Alert, AlertDescription, AlertTitle } from '$lib/ui/core/alert/index.js';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '$lib/ui/core/tabs/index.js';
|
||||
import { Alert, AlertDescription, AlertTitle } from '@evobgp/ui/components/alert/index.js';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@evobgp/ui/components/tabs/index.js';
|
||||
import TenantBirdSettingsCard from '$lib/components/tenant-settings/TenantBirdSettingsCard.svelte';
|
||||
import TenantRevisionSettingsCard from '$lib/components/tenant-settings/TenantRevisionSettingsCard.svelte';
|
||||
import TenantAdditionalSettingsCard from '$lib/components/tenant-settings/TenantAdditionalSettingsCard.svelte';
|
||||
@@ -44,7 +45,7 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-6">
|
||||
<PageShell>
|
||||
<PageHeader
|
||||
title="Параметры tenant"
|
||||
description="Параметры control plane для текущего tenant (API /v1/settings). Токен и тема интерфейса — в разделе «Настройки»."
|
||||
@@ -62,7 +63,7 @@
|
||||
</Alert>
|
||||
|
||||
<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">
|
||||
<TabsTrigger value="bird">BIRD</TabsTrigger>
|
||||
<TabsTrigger value="revision">Ревизии</TabsTrigger>
|
||||
@@ -87,4 +88,4 @@
|
||||
<TenantAdditionalSettingsCard />
|
||||
</TabsContent>
|
||||
</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