add project

This commit is contained in:
2026-05-05 23:10:05 +03:00
parent b2b066e0d3
commit 53c040161a
46 changed files with 3824 additions and 1073 deletions
@@ -0,0 +1,146 @@
import { Component, OnInit, OnDestroy, ViewChild, ElementRef, AfterViewChecked } from '@angular/core';
import {ActivatedRoute, Router} from '@angular/router';
import { CommonModule } from '@angular/common';
import { Subscription, timer, switchMap } from 'rxjs';
import { ProjectService, Project, DockerContainer, ProjectDetailResponse } from '../../../services/project.service';
import { MatIconModule } from '@angular/material/icon';
import { MatButtonModule } from '@angular/material/button';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatDividerModule } from '@angular/material/divider';
import {MatCard, MatCardContent} from '@angular/material/card';
@Component({
selector: 'app-project-details',
standalone: true,
imports: [
CommonModule,
MatIconModule,
MatButtonModule,
MatProgressSpinnerModule,
MatDividerModule,
MatCard,
MatCardContent
],
templateUrl: './project-details.component.html',
styleUrls: ['./project-details.component.scss']
})
export class ProjectDetailsComponent implements OnInit, OnDestroy, AfterViewChecked {
@ViewChild('consoleScroll') private consoleContainer!: ElementRef; // Привязка к #consoleScroll
project?: Project;
config?: any;
containers: DockerContainer[] = [];
logs: string[] = [];
loading = true;
private pollingSub?: Subscription;
private logSub?: Subscription;
constructor(
private route: ActivatedRoute,
private router: Router,
private projectService: ProjectService
) {}
ngOnInit(): void {
const id = this.route.snapshot.paramMap.get('id');
if (id) {
this.loadData(id);
this.startMonitoring(id);
}
}
private loadData(id: string): void {
this.projectService.getProjectDetails(id).subscribe({
next: (data: ProjectDetailResponse) => {
this.project = data.project;
this.config = data.config;
this.loading = false;
this.connectLogs(id);
},
error: (err) => {
console.error('Ошибка загрузки данных проекта:', err);
this.loading = false;
}
});
}
private startMonitoring(id: string): void {
// Опрос состояния контейнеров каждые 5 секунд[cite: 5, 6]
this.pollingSub = timer(0, 5000).pipe(
switchMap(() => this.projectService.getProjectContainers(id))
).subscribe({
next: (data) => this.containers = data,
error: (err) => console.error('Ошибка мониторинга контейнеров:', err)
});
}
private connectLogs(id: string): void {
this.logSub = this.projectService.connectToDockerLogs(id).subscribe({
next: (msg) => this.logs.push(msg),
error: (err) => console.error('WS Error:', err)
});
}
deploy(): void {
if (this.project) this.projectService.deployProject(this.project.id).subscribe();
}
stop(): void {
if (this.project) this.projectService.stopProject(this.project.id).subscribe();
}
ngAfterViewChecked(): void {
this.scrollToBottom();
}
private scrollToBottom(): void {
try {
this.consoleContainer.nativeElement.scrollTop = this.consoleContainer.nativeElement.scrollHeight;
} catch (err) {}
}
ngOnDestroy(): void {
this.pollingSub?.unsubscribe();
this.logSub?.unsubscribe();
}
confirmDelete() {
if (confirm("Уничтожить проект навсегда?")) {
// 1. Сначала «ослепляем» компонент, чтобы он перестал слать запросы
this.pollingSub?.unsubscribe(); // Твой метод, который делает subscription.unsubscribe()
// 2. Только теперь отправляем команду на ликвидацию
if (this.project) this.projectService.deleteProject(this.project.id).subscribe({
next: () => {
console.log("Проект стерт, уходим на дашборд");
this.router.navigate(['/main/user/projects']);
},
error: (err) => {
console.error("Ошибка при удалении, но проект мог уже исчезнуть", err);
// Всё равно уходим, так как оставаться на странице 404 нет смысла
this.router.navigate(['/main/user/projects']);
}
});
}
}
// Метод для окрашивания логов в терминале
formatLogLine(line: string): string {
if (!line) return '';
// Окрашиваем ключевые слова для лучшей читаемости
let formatted = line;
if (line.includes('ERROR') || line.includes('Error') || line.includes('failed')) {
formatted = `<span style="color: #ff5f56; font-weight: bold;">${line}</span>`;
} else if (line.includes('WARN') || line.includes('Warning') || line.includes('Conflict')) {
formatted = `<span style="color: #ffbd2e;">${line}</span>`;
} else if (line.includes('INFO') || line.includes('success') || line.includes('Started')) {
formatted = `<span style="color: #27c93f;">${line}</span>`;
} else if (line.includes('DEBUG')) {
formatted = `<span style="color: #ae81ff;">${line}</span>`;
}
return formatted;
}
}