adjast wss + docker logs
This commit is contained in:
+66
-42
@@ -1,13 +1,14 @@
|
||||
// project-details.component.ts
|
||||
import { Component, OnInit, OnDestroy, ViewChild, ElementRef, AfterViewChecked } from '@angular/core';
|
||||
import {ActivatedRoute, Router} from '@angular/router';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Subscription, timer, switchMap } from 'rxjs';
|
||||
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';
|
||||
import { MatCard, MatCardContent } from '@angular/material/card';
|
||||
|
||||
@Component({
|
||||
selector: 'app-project-details',
|
||||
@@ -25,16 +26,16 @@ import {MatCard, MatCardContent} from '@angular/material/card';
|
||||
styleUrls: ['./project-details.component.scss']
|
||||
})
|
||||
export class ProjectDetailsComponent implements OnInit, OnDestroy, AfterViewChecked {
|
||||
@ViewChild('consoleScroll') private consoleContainer!: ElementRef; // Привязка к #consoleScroll
|
||||
@ViewChild('consoleScroll') private consoleContainer!: ElementRef; // Прив'язка до вікна терміналу
|
||||
|
||||
project?: Project;
|
||||
config?: any;
|
||||
containers: DockerContainer[] = [];
|
||||
logs: string[] = [];
|
||||
logs: string[] = []; // Масив для зберігання рядків логів
|
||||
|
||||
loading = true;
|
||||
private pollingSub?: Subscription;
|
||||
private logSub?: Subscription;
|
||||
private pollingSub?: Subscription; // Підписка на моніторинг контейнерів
|
||||
private logSub?: Subscription; // Підписка на потік логів через STOMP
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
@@ -50,46 +51,90 @@ export class ProjectDetailsComponent implements OnInit, OnDestroy, AfterViewChec
|
||||
}
|
||||
}
|
||||
|
||||
// Завантаження основних даних проекту
|
||||
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);
|
||||
console.error('Помилка завантаження проекту:', err);
|
||||
this.loading = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Опитування стану контейнерів кожні 5 секунд
|
||||
private startMonitoring(id: string): void {
|
||||
// Опрос состояния контейнеров каждые 5 секунд[cite: 5, 6]
|
||||
this.pollingSub = timer(0, 5000).pipe(
|
||||
switchMap(() => this.projectService.getProjectContainers(id))
|
||||
switchMap(() => this.projectService.getProjectContainers(id).pipe(
|
||||
catchError(() => of([])) // У разі помилки повертаємо порожній список
|
||||
))
|
||||
).subscribe({
|
||||
next: (data) => this.containers = data,
|
||||
error: (err) => console.error('Ошибка мониторинга контейнеров:', err)
|
||||
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)
|
||||
});
|
||||
}
|
||||
|
||||
private connectLogs(id: string): void {
|
||||
// Підключення до трансляції логів
|
||||
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),
|
||||
error: (err) => console.error('WS Error:', err)
|
||||
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.projectService.deployProject(this.project.id).subscribe();
|
||||
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();
|
||||
}
|
||||
@@ -103,44 +148,23 @@ export class ProjectDetailsComponent implements OnInit, OnDestroy, AfterViewChec
|
||||
ngOnDestroy(): void {
|
||||
this.pollingSub?.unsubscribe();
|
||||
this.logSub?.unsubscribe();
|
||||
this.projectService.disconnectLogs(); // Закриваємо з'єднання з брокером
|
||||
}
|
||||
|
||||
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')) {
|
||||
} else if (line.includes('WARN') || line.includes('Warning')) {
|
||||
formatted = `<span style="color: #ffbd2e;">${line}</span>`;
|
||||
} else if (line.includes('INFO') || line.includes('success') || line.includes('Started')) {
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user