Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1c7152a285 | ||
|
|
a37c931ee7 |
No files matched your search
@@ -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 для локального и эталонного развёртывания.
|
||||
|
||||
## Документация
|
||||
|
||||
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -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"
|
||||
},
|
||||
File renamed without changes.
@@ -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",
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -0,0 +1 @@
|
||||
export * from '@evobgp/shared/types/api.js';
|
||||
File renamed without changes.
File renamed without changes.
|
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';
|
||||
File renamed without changes.
@@ -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';
|
||||
File renamed without changes.
@@ -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;
|
||||
File renamed without changes.
+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';
|
||||
File renamed without changes.
+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[];
|
||||
File renamed without changes.
+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);
|
||||
File renamed without changes.
+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>
|
||||
File renamed without changes.
@@ -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';
|
||||
|
||||
File renamed without changes.
@@ -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>
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Loaded 100 of 379 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user