171 lines
6.1 KiB
TypeScript
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;
|
|
}
|
|
}
|