diff --git a/jambotron-ui/src/app/modules/user-module/project-details.component/project-details.component.html b/jambotron-ui/src/app/modules/user-module/project-details.component/project-details.component.html index b0c6b3c..12b97d6 100644 --- a/jambotron-ui/src/app/modules/user-module/project-details.component/project-details.component.html +++ b/jambotron-ui/src/app/modules/user-module/project-details.component/project-details.component.html @@ -54,78 +54,66 @@ -
-
+
+
-
+
Маршрутизация сети: -
+
- - - - {{ domain }} + open_in_new + {{ domain }} - - :{{ getContainerPort(container.name) }} + + порт {{ getContainerPort(container.name) }}
- Локальный стек (нет публичного прокси) + Локальный стек (нет публичного прокси)
-
-
-
- Настройка шлюза -
- -
-
-
- - .jamb -
+
+ + Поддомен + + .jambotron.com + -
- -
+ + Порт + +
- } diff --git a/jambotron-ui/src/app/modules/user-module/project-details.component/project-details.component.scss b/jambotron-ui/src/app/modules/user-module/project-details.component/project-details.component.scss index 0efe77b..ba6a14e 100644 --- a/jambotron-ui/src/app/modules/user-module/project-details.component/project-details.component.scss +++ b/jambotron-ui/src/app/modules/user-module/project-details.component/project-details.component.scss @@ -104,3 +104,19 @@ overflow-y: auto; } } + +.mat-mdc-form-field { + width: 100%; + --mdc-outlined-text-field-container-shape: 6px; + --mdc-outlined-text-field-input-text-color: #e5e7eb; + --mdc-outlined-text-field-focus-input-text-color: #ffffff; + --mdc-outlined-text-field-label-text-color: #6b7280; + --mdc-outlined-text-field-focus-label-text-color: #3b82f6; + --mdc-outlined-text-field-outline-color: #1f2937; + --mdc-outlined-text-field-focus-outline-color: #2563eb; + + .mat-mdc-text-field-wrapper { + background-color: rgba(3, 7, 18, 0.6) !important; // Глубокий тёмный фон bg-gray-950 + } +} + diff --git a/jambotron-ui/src/app/modules/user-module/project-details.component/project-details.component.ts b/jambotron-ui/src/app/modules/user-module/project-details.component/project-details.component.ts index fd38113..5d638ef 100644 --- a/jambotron-ui/src/app/modules/user-module/project-details.component/project-details.component.ts +++ b/jambotron-ui/src/app/modules/user-module/project-details.component/project-details.component.ts @@ -16,6 +16,8 @@ import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { MatDividerModule } from '@angular/material/divider'; import { MatCard, MatCardContent } from '@angular/material/card'; import {FormsModule} from '@angular/forms'; +import {MatInputModule} from '@angular/material/input'; +import {MatFormFieldModule} from '@angular/material/form-field'; @Component({ selector: 'app-project-details', @@ -28,7 +30,9 @@ import {FormsModule} from '@angular/forms'; MatProgressSpinnerModule, MatDividerModule, MatCard, - MatCardContent + MatCardContent, + MatFormFieldModule, // <-- Включаем в сборку созидания + MatInputModule // <-- Включаем в сборку созидания ], templateUrl: './project-details.component.html', styleUrls: ['./project-details.component.scss'] @@ -252,19 +256,18 @@ export class ProjectDetailsComponent implements OnInit, OnDestroy, AfterViewChec // Хранит состояние редактирования для каждого контейнера: { [containerId]: boolean } editingDomains: { [key: string]: boolean } = {}; - // Временные переменные для хранения ввода пользователя во время редактирования + // Временные переменные для хранения ввода пользователя во время инлайн-редактирования tempSubdomain: string = ''; tempPort: number = 80; /** - * Извлекает имя сервиса (например, 'web') из полного имени Docker-контейнера + * Безопасно извлекает имя сервиса (например, 'web') из имени Docker-контейнера. + * Проверяет префиксы как по ID, так и по Name проекта. */ getServiceName(containerName: string): string | null { - if (!containerName || !this.project?.id) return null; + if (!containerName || !this.project) return null; - // Проверяем префикс по ID проекта const prefixId = `jambotron-${this.project.id}-`; - // Резервный префикс по имени проекта (на случай, если бэкенд использует его) const prefixName = `jambotron-${this.project.name}-`; let servicePart = ''; @@ -274,7 +277,7 @@ export class ProjectDetailsComponent implements OnInit, OnDestroy, AfterViewChec } else if (containerName.startsWith(prefixName)) { servicePart = containerName.substring(prefixName.length); } else { - // Фолбэк-разбор для коротких имён, если префиксы не подошли + // Резервный разбор по токенам (jambotron-проект-сервис) const parts = containerName.split('-'); if (parts.length >= 3 && parts[0] === 'jambotron') { servicePart = parts.slice(2).join('-'); @@ -283,7 +286,7 @@ export class ProjectDetailsComponent implements OnInit, OnDestroy, AfterViewChec } } - // Отрезаем индекс реплики Compose (например, "-1") + // Отрезаем индекс реплики Docker Compose (например, "-1") if (servicePart.endsWith('-1')) { servicePart = servicePart.substring(0, servicePart.length - 2); } @@ -291,7 +294,7 @@ export class ProjectDetailsComponent implements OnInit, OnDestroy, AfterViewChec } /** - * Возвращает текущий сформированный домен для отображения + * Возвращает текущий сформированный домен для вывода в UI */ getContainerDomain(containerName: string): string | null { const serviceName = this.getServiceName(containerName); @@ -302,7 +305,7 @@ export class ProjectDetailsComponent implements OnInit, OnDestroy, AfterViewChec } /** - * Возвращает текущий порт сервиса + * Возвращает порт сервиса из конфигурации MongoDB */ getContainerPort(containerName: string): number { const serviceName = this.getServiceName(containerName); @@ -313,28 +316,31 @@ export class ProjectDetailsComponent implements OnInit, OnDestroy, AfterViewChec } /** - * Вход в режим редактирования плашки + * Активация режима редактирования. Теперь полностью безопасна. */ startEditDomain(container: any) { const serviceName = this.getServiceName(container.name); - if (!serviceName) return; + if (!serviceName) { + console.warn(`[Jambotron] Не удалось определить имя сервиса для: ${container.name}`); + return; + } + // Переключаем реактивный флаг видимости формы в шаблоне this.editingDomains[container.id] = true; - // Инициализируем временные переменные текущими значениями из Mongo конфигурации + // Подтягиваем текущие сохранённые значения из локального объекта конфигурации config const currentConfig = this.config?.serviceConfigs?.[serviceName]; this.tempSubdomain = currentConfig?.subdomain || ''; this.tempPort = currentConfig?.port || 80; } /** - * Сохранение изменённого домена и порта на бэкенд + * Сохранение изменённого поддомена и порта в MongoDB базы данных */ saveDomainConfig(container: any) { const serviceName = this.getServiceName(container.name); if (!serviceName || !this.project?.id || !this.config) return; - // Гарантируем существование мапы конфигураций if (!this.config.serviceConfigs) { this.config.serviceConfigs = {}; } @@ -342,7 +348,7 @@ export class ProjectDetailsComponent implements OnInit, OnDestroy, AfterViewChec const subdomainClean = this.tempSubdomain.trim().toLowerCase(); if (!subdomainClean) { - // Если поддомен стёрли — удаляем настройку проксирования для этого сервиса + // Если поле очищено — удаляем проксирование delete this.config.serviceConfigs[serviceName]; } else { this.config.serviceConfigs[serviceName] = { @@ -351,15 +357,16 @@ export class ProjectDetailsComponent implements OnInit, OnDestroy, AfterViewChec }; } - // Отправляем монолитный конфиг в базу MongoDB через твой сохранённый сервис + // Отправка монолитного конфига на бэкенд через твой рабочий ProjectService this.projectService.updateProjectConfig(this.project.id, this.config).subscribe({ next: (updatedConfig) => { this.config = updatedConfig; this.editingDomains[container.id] = false; - console.log(`Конфигурация Caddy для ${serviceName} успешно зафиксирована.`); + console.log(`[Caddy] Маршрут для сервиса ${serviceName} успешно сохранён.`); }, error: (err) => { - alert(err.error?.message || 'Ошибка валидации доменного имени.'); + console.error('Ошибка обновления конфигурации проекта:', err); + alert(err.error?.message || 'Не удалось применить конфигурацию шлюза.'); } }); } diff --git a/src/main/java/com/jambotronGroup/jambotron/projects/ProjectService.java b/src/main/java/com/jambotronGroup/jambotron/projects/ProjectService.java index 3b438a0..201c8a7 100644 --- a/src/main/java/com/jambotronGroup/jambotron/projects/ProjectService.java +++ b/src/main/java/com/jambotronGroup/jambotron/projects/ProjectService.java @@ -155,12 +155,13 @@ public class ProjectService { try { Files.createDirectories(projectPath); - // Записываем уже модифицированный файл со всеми доменами + // Записываем модифицированный файл со всеми доменами Files.writeString(projectPath.resolve("docker-compose.yml"), finalComposeContent); logToConsole(projectId, "Конфигурация Caddy подготовлена. Запуск контейнеров..."); - ProcessBuilder pb = new ProcessBuilder("docker", "compose", "up", "-d", "--remove-orphans"); + // Добавляем --force-recreate, чтобы Docker гарантированно применил новые labels! + ProcessBuilder pb = new ProcessBuilder("docker", "compose", "up", "-d", "--force-recreate", "--remove-orphans"); pb.directory(projectPath.toFile()); Process process = pb.start();