Files
jambotron/jambotron-ui/src/app/modules/user-module/project-details.component/project-details.component.ts
T
2026-05-11 13:10:00 +03:00

171 lines
6.1 KiB
TypeScript

// project-details.component.ts
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, catchError, of } 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; // Прив'язка до вікна терміналу
project?: Project;
config?: any;
containers: DockerContainer[] = [];
logs: string[] = []; // Масив для зберігання рядків логів
loading = true;
private pollingSub?: Subscription; // Підписка на моніторинг контейнерів
private logSub?: Subscription; // Підписка на потік логів через STOMP
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;
},
error: (err) => {
console.error('Помилка завантаження проекту:', err);
this.loading = false;
}
});
}
// Опитування стану контейнерів кожні 5 секунд
private startMonitoring(id: string): void {
this.pollingSub = timer(0, 5000).pipe(
switchMap(() => this.projectService.getProjectContainers(id).pipe(
catchError(() => of([])) // У разі помилки повертаємо порожній список
))
).subscribe({
next: (data: DockerContainer[]) => {
this.containers = data;
// Якщо з'явилися контейнери і ми ще не підключені до логів — підключаємося
if (this.containers.length > 0 && !this.logSub) {
const mainContainer = this.containers.find(c => c.state === 'running') || this.containers[0];
this.connectLogs(mainContainer.id);
}
},
error: (err) => console.error('Помилка моніторингу:', err)
});
}
// Підключення до трансляції логів
connectLogs(id: string): void {
console.log(`Підключення до логів контейнера: ${id}`);
// Очищаємо попередню підписку перед створенням нової
if (this.logSub) {
this.logSub.unsubscribe();
this.projectService.disconnectLogs();
}
this.logs = []; // Очищаємо консоль при зміні контейнера
this.logSub = this.projectService.connectToDockerLogs(id).subscribe({
next: (msg) => {
this.logs.push(msg);
// Обмежуємо кількість рядків у пам'яті (останні 200)
if (this.logs.length > 200) this.logs.shift();
},
error: (err) => console.error('STOMP Error:', err)
});
}
deploy(): void {
if (this.project) {
this.loading = true;
this.projectService.deployProject(this.project.id).subscribe({
next: () => this.loading = false,
error: () => this.loading = false
});
}
}
stop(): void {
if (this.project) this.projectService.stopProject(this.project.id).subscribe();
}
confirmDelete() {
if (confirm("Знищити проект назавжди?")) {
this.pollingSub?.unsubscribe(); // Зупиняємо оновлення інтерфейсу
if (this.project) {
this.projectService.deleteProject(this.project.id).subscribe({
next: () => this.router.navigate(['/main/user/projects']),
error: () => this.router.navigate(['/main/user/projects'])
});
}
}
}
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();
this.projectService.disconnectLogs(); // Закриваємо з'єднання з брокером
}
// Форматування для відображення кольорів у терміналі
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')) {
formatted = `<span style="color: #ffbd2e;">${line}</span>`;
} else if (line.includes('INFO') || line.includes('success')) {
formatted = `<span style="color: #27c93f;">${line}</span>`;
} else if (line.includes('DEBUG')) {
formatted = `<span style="color: #ae81ff;">${line}</span>`;
}
return formatted;
}
}