refactor(web): общие форматтеры, единая навигация, RBAC-гейтинг и чистка мёртвого кода
- lib/format.ts: ru-RU форматтеры дат/чисел в одном месте — убраны дубли из 8 компонентов (agent-card, fleet-grid, blocked-ips/ports, facts, lifecycle, host-firewall, lists-columns, system-monitor) - lib/nav.ts: единый конфиг маршрутов — sidebar, ⌘K-поиск и breadcrumbs рендерятся из одного источника (было 3 расходящихся копии) - lib/permissions.ts + useCan: клиентский RBAC — кнопки создания/подтверждения скрываются без fw:*:write/admin (сервер остаётся авторитетным) - удалён мёртвый код ~2500 строк: stepper, data-grid dnd/virtual/visibility варианты, settings-shell
This commit is contained in:
@@ -47,6 +47,7 @@ import { ConfirmDialog } from '@/components/confirm-dialog'
|
||||
import { QueryState } from '@/components/query-state'
|
||||
import { agentsQueryOptions, settingsQueryOptions } from '@/queries'
|
||||
import { apiFetch } from '@/lib/api'
|
||||
import { useCan } from '@/lib/permissions'
|
||||
import { useCopyToClipboard } from '@/hooks/use-copy-to-clipboard'
|
||||
import { Button } from '@evofw/ui/components/button'
|
||||
import {
|
||||
@@ -97,6 +98,7 @@ function AgentsPage() {
|
||||
const agentsQ = useQuery(agentsQueryOptions())
|
||||
const settingsQ = useQuery(settingsQueryOptions())
|
||||
const { copyToClipboard } = useCopyToClipboard()
|
||||
const canWrite = useCan()('fw:agents:write')
|
||||
const [createOpen, setCreateOpen] = useState(false)
|
||||
const [deleteAgentId, setDeleteAgentId] = useState<string | null>(null)
|
||||
const [filters, setFilters] = useState<Filter[]>([])
|
||||
@@ -418,12 +420,12 @@ function AgentsPage() {
|
||||
</ToggleGroup>
|
||||
)
|
||||
|
||||
const addButton = (
|
||||
const addButton = canWrite ? (
|
||||
<Button size="sm" onClick={() => setCreateOpen(true)}>
|
||||
<Plus data-icon="inline-start" />
|
||||
Добавить агента
|
||||
</Button>
|
||||
)
|
||||
) : null
|
||||
|
||||
return (
|
||||
<PageShell>
|
||||
@@ -454,6 +456,7 @@ function AgentsPage() {
|
||||
>
|
||||
Показать
|
||||
</Button>
|
||||
{canWrite ? (
|
||||
<Button
|
||||
size="sm"
|
||||
disabled={
|
||||
@@ -464,6 +467,7 @@ function AgentsPage() {
|
||||
<Check data-icon="inline-start" />
|
||||
Approve all
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
</FrameHeader>
|
||||
</Frame>
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
import { ConfirmDialog } from '@/components/confirm-dialog'
|
||||
import { listsQueryOptions, evobgpCommunitiesQueryOptions } from '@/queries'
|
||||
import { apiFetch } from '@/lib/api'
|
||||
import { useCan } from '@/lib/permissions'
|
||||
import {
|
||||
Autocomplete,
|
||||
AutocompleteContent,
|
||||
@@ -67,6 +68,7 @@ const CREATE_SOURCE_ITEMS = [
|
||||
function ListsPage() {
|
||||
const navigate = useNavigate()
|
||||
const qc = useQueryClient()
|
||||
const canWrite = useCan()('fw:lists:write')
|
||||
const [createOpen, setCreateOpen] = useState(false)
|
||||
const [name, setName] = useState('')
|
||||
const [source, setSource] = useState<CreateSource>('static')
|
||||
@@ -199,10 +201,12 @@ function ListsPage() {
|
||||
/>
|
||||
Обновить
|
||||
</Button>
|
||||
<Button size="sm" onClick={() => setCreateOpen(true)}>
|
||||
<Plus data-icon="inline-start" />
|
||||
Создать
|
||||
</Button>
|
||||
{canWrite ? (
|
||||
<Button size="sm" onClick={() => setCreateOpen(true)}>
|
||||
<Plus data-icon="inline-start" />
|
||||
Создать
|
||||
</Button>
|
||||
) : null}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -13,6 +13,7 @@ import { ConfirmDialog } from '@/components/confirm-dialog'
|
||||
import { PolicySetIcon } from '@/components/rules/policy-set-icon'
|
||||
import { policySetsQueryOptions } from '@/queries'
|
||||
import { apiFetch } from '@/lib/api'
|
||||
import { useCan } from '@/lib/permissions'
|
||||
import { Button } from '@evofw/ui/components/button'
|
||||
import { Field, FieldLabel } from '@evofw/ui/components/field'
|
||||
import { Input } from '@evofw/ui/components/input'
|
||||
@@ -200,12 +201,13 @@ function PolicySetsPage() {
|
||||
[navigate],
|
||||
)
|
||||
|
||||
const addButton = (
|
||||
const canCreate = useCan()('fw:policies:write')
|
||||
const addButton = canCreate ? (
|
||||
<Button size="sm" onClick={() => setSheetOpen(true)}>
|
||||
<Plus data-icon="inline-start" />
|
||||
Новый набор
|
||||
</Button>
|
||||
)
|
||||
) : null
|
||||
|
||||
return (
|
||||
<PageShell>
|
||||
|
||||
@@ -14,12 +14,12 @@ import { LoadingButton } from '@/components/loading-button'
|
||||
import { SettingRow } from '@/components/setting-row'
|
||||
import { settingsQueryOptions } from '@/queries'
|
||||
import { apiFetch } from '@/lib/api'
|
||||
import { useCan } from '@/lib/permissions'
|
||||
import { Input } from '@evofw/ui/components/input'
|
||||
import { Switch } from '@evofw/ui/components/switch'
|
||||
|
||||
/**
|
||||
* Control plane settings — single page: PageShell + Frame + SettingRow.
|
||||
* SettingsShell (reui-kit) — только при 2+ секциях.
|
||||
* Preview: https://reui.io/preview/base/settings-16 · https://reui.io/preview/base/settings-3
|
||||
*/
|
||||
|
||||
@@ -30,6 +30,7 @@ export const Route = createFileRoute('/_auth/settings')({
|
||||
function SettingsPage() {
|
||||
const qc = useQueryClient()
|
||||
const settingsQ = useQuery(settingsQueryOptions())
|
||||
const canSave = useCan()('fw:settings:admin')
|
||||
const [form, setForm] = useState<Record<string, string>>({})
|
||||
|
||||
// Seed the form once — a background refetch must not wipe in-progress edits.
|
||||
@@ -134,16 +135,18 @@ function SettingsPage() {
|
||||
</div>
|
||||
</FramePanel>
|
||||
</Frame>
|
||||
<div className="flex justify-end">
|
||||
<LoadingButton
|
||||
onClick={() => save.mutate()}
|
||||
isLoading={save.isPending}
|
||||
disabled={!initialized.current}
|
||||
loadingLabel="Сохранение…"
|
||||
>
|
||||
Сохранить
|
||||
</LoadingButton>
|
||||
</div>
|
||||
{canSave ? (
|
||||
<div className="flex justify-end">
|
||||
<LoadingButton
|
||||
onClick={() => save.mutate()}
|
||||
isLoading={save.isPending}
|
||||
disabled={!initialized.current}
|
||||
loadingLabel="Сохранение…"
|
||||
>
|
||||
Сохранить
|
||||
</LoadingButton>
|
||||
</div>
|
||||
) : null}
|
||||
</PageShell>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user