diff --git a/ui/src/app/knock/FORM_ARRAY_DOCUMENTATION.md b/ui/src/app/knock/FORM_ARRAY_DOCUMENTATION.md new file mode 100644 index 0000000..dfa84e2 --- /dev/null +++ b/ui/src/app/knock/FORM_ARRAY_DOCUMENTATION.md @@ -0,0 +1,348 @@ +# Документация: FormArray в компоненте KnockPageComponent + +## Обзор + +В компоненте `KnockPageComponent` используется Angular FormArray для управления динамическими формами целей (targets) в режиме "form". Это позволяет пользователям добавлять, удалять и редактировать неограниченное количество целей для пропинывания портов. + +## Архитектура FormArray + +### 1. Структура данных + +```typescript +form = this.fb.group({ + // ... другие поля + targetForms: this.fb.array([]) // FormArray для динамических форм +}); + +get targetForms(): FormArray { + return this.form.get('targetForms') as FormArray; +} +``` + +### 2. Структура отдельной формы цели + +Каждая форма цели содержит следующие поля: + +```typescript +private createTargetForm(): FormGroup { + return this.fb.group({ + protocol: ['tcp', Validators.required], // Протокол (TCP/UDP) + host: ['127.0.0.1', Validators.required], // IP адрес хоста + port: [22, [Validators.required, Validators.min(1), Validators.max(65535)]], // Порт + gateway: [''] // Шлюз (опционально) + }); +} +``` + +## Основные методы работы с FormArray + +### 1. Создание новой формы цели + +```typescript +addTarget(): void { + const newTargetForm = this.createTargetForm(); + this.targetForms.push(newTargetForm); + this.serializeFormTargets(); +} +``` + +**Что происходит:** + +- Создается новая FormGroup с полями по умолчанию +- Форма добавляется в FormArray через `push()` +- Автоматически вызывается сериализация данных + +### 2. Удаление формы цели + +```typescript +removeTarget(index: number): void { + if (this.targetForms.length > 1) { + this.targetForms.removeAt(index); + this.serializeFormTargets(); + } +} +``` + +**Особенности:** + +- Защита от удаления последней формы (минимум 1 форма) +- Используется `removeAt(index)` для удаления по индексу +- Автоматическая сериализация после удаления + +### 3. Сериализация данных форм + +```typescript +private serializeFormTargets(): void { + if (this.form.value.mode !== 'form') return; + + const targets: string[] = []; + this.targetForms.controls.forEach(targetForm => { + const value = targetForm.value; + if (value.protocol && value.host && value.port) { + let targetString = `${value.protocol}:${value.host}:${value.port}`; + if (value.gateway && value.gateway.trim()) { + targetString += `:${value.gateway.trim()}`; + } + targets.push(targetString); + } + }); + + this.form.patchValue({ targets: targets.join(';') }); +} +``` + +**Процесс сериализации:** + +1. Проверка, что текущий режим - "form" +2. Итерация по всем формам в FormArray +3. Сборка строки в формате `protocol:host:port:gateway` +4. Объединение всех целей через `;` +5. Обновление поля `targets` в основной форме + +## Интеграция с HTML шаблоном + +### 1. Отображение форм + +```html +
+
+ +
+
+``` + +**Ключевые моменты:** + +- `*ngFor` итерируется по `targetForms.controls` +- `[formGroup]` связывает каждую форму с FormGroup +- `$any(targetForm)` решает проблему типизации TypeScript + +### 2. Поля формы + +```html +
+ + +
+``` + +## Автоматическое сохранение и восстановление + +### 1. Подписка на изменения + +```typescript +private setupAutoSave() { + // Подписка на изменения в формах целей + this.targetForms.valueChanges.subscribe(() => { + if (this.form.value.mode === 'form') { + this.serializeFormTargets(); + setTimeout(() => this.saveStateToLocalStorage(), 300); + } + }); +} +``` + +### 2. Сохранение в localStorage + +```typescript +private saveStateToLocalStorage() { + const state: any = { + // ... другие поля + }; + + // Сохраняем данные форм целей для режима form + if (formValue.mode === 'form' && this.targetForms.length > 0) { + state.targetForms = this.targetForms.value; + } + + localStorage.setItem(this.STORAGE_KEY, JSON.stringify(state)); +} +``` + +### 3. Восстановление из localStorage + +```typescript +private loadStateFromLocalStorage() { + // ... загрузка других полей + + // Загружаем сохраненные формы целей для режима form + if (state.mode === 'form' && state.targetForms && Array.isArray(state.targetForms)) { + this.targetForms.clear(); + state.targetForms.forEach((targetData: any) => { + const targetForm = this.fb.group({ + protocol: [targetData.protocol || 'tcp', Validators.required], + host: [targetData.host || '127.0.0.1', Validators.required], + port: [targetData.port || 22, [Validators.required, Validators.min(1), Validators.max(65535)]], + gateway: [targetData.gateway || ''] + }); + this.targetForms.push(targetForm); + }); + } +} +``` + +## Преобразование между режимами + +### 1. Конвертация в режим Form + +```typescript +private convertInlineToForm() { + const targetsString = this.form.value.targets || ''; + const targets = targetsString.split(';').filter(t => t.trim()); + + targets.forEach(target => { + const parts = target.trim().split(':'); + if (parts.length >= 3) { + const targetForm = this.fb.group({ + protocol: [parts[0] || 'tcp', Validators.required], + host: [parts[1] || '127.0.0.1', Validators.required], + port: [parseInt(parts[2]) || 22, [Validators.required, Validators.min(1), Validators.max(65535)]], + gateway: [parts[3] || ''] + }); + this.targetForms.push(targetForm); + } + }); +} +``` + +### 2. Конвертация из режима Form + +```typescript +private handleModeChangeFromForm(previousMode: string, newMode: string) { + // Сначала сериализуем данные формы + this.serializeFormTargets(); + + if (newMode === 'inline') { + // Данные уже в targets, ничего дополнительно не нужно + } else if (newMode === 'yaml') { + this.convertInlineToYaml(); + } +} +``` + +## Жизненный цикл FormArray + +### 1. Инициализация + +```typescript +private initializeFormMode(): void { + // Если нет форм целей, создаем одну по умолчанию + if (this.targetForms.length === 0) { + this.addTarget(); + } +} +``` + +### 2. Очистка при смене режима + +```typescript +private handleModeChangeToForm(previousMode: string) { + // Очищаем существующие формы + this.targetForms.clear(); + + // Конвертируем данные из предыдущего режима + if (previousMode === 'inline') { + this.convertInlineToForm(); + } else if (previousMode === 'yaml') { + this.convertYamlToForm(); + } + + // Инициализируем формы если их нет + if (this.targetForms.length === 0) { + this.addTarget(); + } +} +``` + +## Валидация + +### 1. Валидация полей формы + +```typescript +private createTargetForm(): FormGroup { + return this.fb.group({ + protocol: ['tcp', Validators.required], + host: ['127.0.0.1', Validators.required], + port: [22, [Validators.required, Validators.min(1), Validators.max(65535)]], + gateway: [''] + }); +} +``` + +### 2. Защита от удаления всех форм + +```typescript +removeTarget(index: number): void { + if (this.targetForms.length > 1) { + this.targetForms.removeAt(index); + this.serializeFormTargets(); + } +} +``` + +## Преимущества использования FormArray + +1. **Динамичность**: Возможность добавления/удаления форм в runtime +2. **Валидация**: Встроенная валидация для каждой формы +3. **Реактивность**: Автоматическое обновление UI при изменениях +4. **Типобезопасность**: TypeScript поддержка +5. **Интеграция**: Легкая интеграция с Angular Reactive Forms +6. **Сериализация**: Простое преобразование в различные форматы + +## Потенциальные проблемы и решения + +### 1. Проблема типизации в шаблоне + +**Проблема:** +```html +[formGroup]="targetForm" +``` + +**Решение:** +```html +[formGroup]="$any(targetForm)" +``` + +### 2. Защита от пустого FormArray + +**Проблема:** Пользователь может удалить все формы + +**Решение:** +```typescript +removeTarget(index: number): void { + if (this.targetForms.length > 1) { // Минимум 1 форма + this.targetForms.removeAt(index); + } +} +``` + +### 3. Производительность при большом количестве форм + +**Проблема:** Много форм может замедлить приложение + +**Решение:** Виртуализация или пагинация (не реализовано в текущей версии) + +## Заключение + +FormArray в данном компоненте обеспечивает гибкую и мощную систему управления динамическими формами. Реализация включает: + +- Полный жизненный цикл форм (создание, редактирование, удаление) +- Автоматическую сериализацию/десериализацию +- Интеграцию с системой режимов +- Сохранение состояния в localStorage +- Валидацию и защиту от некорректных данных + +Этот подход делает компонент удобным для пользователей и легко расширяемым для разработчиков. diff --git a/ui/src/app/knock/knock-page.component.html b/ui/src/app/knock/knock-page.component.html index 30b4cb6..4d7e104 100644 --- a/ui/src/app/knock/knock-page.component.html +++ b/ui/src/app/knock/knock-page.component.html @@ -71,7 +71,7 @@ /> -
+
Wait connection
-
+ + +
+
+ +
+
+
+

Target {{ i + 1 }}

+ +
+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+
+
+ +
+ +
+
+
+
@@ -171,7 +258,7 @@ >
-
+
-
+
-
+
-
+