diff --git a/client/src/components/ApiScopesEditor.tsx b/client/src/components/ApiScopesEditor.tsx new file mode 100644 index 0000000..5db6c80 --- /dev/null +++ b/client/src/components/ApiScopesEditor.tsx @@ -0,0 +1,159 @@ +import { useQuery } from '@tanstack/react-query'; +import { Checkbox } from '@/components/ui/checkbox'; +import { Label } from '@/components/ui/label'; +import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'; + +// Скоупы API-ключа: режим + ограничения по формам/справочникам (null = все) +export interface ApiScopesValue { + mode: 'read' | 'write' | 'full'; + formIds: number[] | null; + tableIds: number[] | null; +} + +// Дефолт для нового ключа: чтение, всё доступно +export const DEFAULT_API_SCOPES: ApiScopesValue = { mode: 'read', formIds: null, tableIds: null }; + +// Валидация: при выборочном доступе нужна хотя бы одна форма и один справочник +export function isApiScopesInvalid(value: ApiScopesValue): boolean { + if (value.mode === 'full') return false; + return (value.formIds !== null && value.formIds.length === 0) + || (value.tableIds !== null && value.tableIds.length === 0); +} + +type ScopeItem = { id: number; name: string }; + +type ApiScopesEditorProps = { + value: ApiScopesValue | null; // null — полный доступ (legacy) + onChange: (value: ApiScopesValue) => void; + idPrefix?: string; // префикс id/htmlFor элементов (при нескольких редакторах на странице) +}; + +// Редактор скоупов API-ключа: радиогруппа режима доступа и (при read/write) +// блоки «Доступ к формам» / «Доступ к справочникам» («Все / Выбранные» + чекбоксы). +// Списки форм и справочников загружает сам. +export function ApiScopesEditor({ value, onChange, idPrefix = 'scopes' }: ApiScopesEditorProps) { + const current: ApiScopesValue = value ?? { mode: 'full', formIds: null, tableIds: null }; + const allForms = current.formIds == null; + const allTables = current.tableIds == null; + + const { data: formsData } = useQuery<{ success: boolean; forms: ScopeItem[] }>({ + queryKey: ['/api/forms'], + }); + const { data: directoriesData } = useQuery<{ tables: ScopeItem[] }>({ + queryKey: ['/api/directories'], + }); + const forms = formsData?.forms ?? []; + const directories = directoriesData?.tables ?? []; + + const setMode = (mode: ApiScopesValue['mode']) => { + if (mode === 'full') { + onChange({ mode, formIds: null, tableIds: null }); + } else { + onChange({ ...current, mode }); + } + }; + + const toggleId = (list: number[], id: number) => + list.includes(id) ? list.filter((x) => x !== id) : [...list, id]; + + // Блок выбора «Все / Выбранные» со скроллируемым списком чекбоксов + const renderScopePicker = ( + title: string, + allLabel: string, + all: boolean, + setAll: (v: boolean) => void, + items: ScopeItem[], + selectedIds: number[], + setSelectedIds: (ids: number[]) => void, + prefix: string, + ) => ( +
+ Выберите хотя бы одну форму и один справочник или переключитесь на «Все» +
+ )} + > + )} +{describeAction(entry)}
+ {newBotCredentials.apiKey}
+
+
+