Няма описание
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. import { Component, OnInit, OnDestroy, ViewChild, ElementRef, AfterViewChecked, Input } from '@angular/core';
  2. import { CommonModule } from '@angular/common';
  3. import { MatCardModule } from '@angular/material/card';
  4. import { MatButtonModule } from '@angular/material/button';
  5. import { MatIconModule } from '@angular/material/icon';
  6. import { MatSelectModule } from '@angular/material/select';
  7. import { MatFormFieldModule } from '@angular/material/form-field';
  8. import { MatCheckboxModule } from '@angular/material/checkbox';
  9. import { MatTooltipModule } from '@angular/material/tooltip';
  10. import { FormsModule } from '@angular/forms';
  11. import { Subscription } from 'rxjs';
  12. import { LogService } from '../services/log.service';
  13. import { SessionService } from '../services/session.service';
  14. import { AuthService } from '../services/auth.service';
  15. import { LogEntry } from '../models/log.model';
  16. import { Session } from '../models/session.model';
  17. @Component({
  18. selector: 'app-log-display',
  19. standalone: true,
  20. imports: [
  21. CommonModule,
  22. MatCardModule,
  23. MatButtonModule,
  24. MatIconModule,
  25. MatSelectModule,
  26. MatFormFieldModule,
  27. MatCheckboxModule,
  28. MatTooltipModule,
  29. FormsModule
  30. ],
  31. templateUrl: './log-display.component.html',
  32. styleUrl: './log-display.component.scss'
  33. })
  34. export class LogDisplayComponent implements OnInit, OnDestroy, AfterViewChecked {
  35. @Input() instanceId?: string; // 实例ID参数,用于过滤该实例的会话日志
  36. @ViewChild('logContent') private logContent!: ElementRef;
  37. allLogs: LogEntry[] = [];
  38. filteredLogs: LogEntry[] = [];
  39. availableSessions: Session[] = [];
  40. selectedLevel = 'all';
  41. selectedSessionId = 'all';
  42. autoScroll = true;
  43. isPaused = false;
  44. private subscriptions: Subscription = new Subscription();
  45. private shouldScroll = false;
  46. constructor(
  47. private logService: LogService,
  48. private sessionService: SessionService,
  49. private authService: AuthService
  50. ) {}
  51. ngOnInit() {
  52. // 订阅日志流
  53. this.subscriptions.add(
  54. this.logService.logStream$.subscribe(log => {
  55. if (!this.isPaused) {
  56. this.allLogs.push(log);
  57. this.applyFilters();
  58. this.shouldScroll = this.autoScroll;
  59. }
  60. })
  61. );
  62. // 订阅会话列表(用于过滤)- 使用BehaviorSubject避免重复请求
  63. this.subscriptions.add(
  64. this.sessionService.sessions$.subscribe(sessions => {
  65. this.availableSessions = sessions;
  66. // 如果有instanceId,过滤属于该实例的会话(移除实例过滤,直接使用所有会话)
  67. // 注意:instanceService已移除,不再支持实例过滤
  68. })
  69. );
  70. // 初始化一些示例日志
  71. this.addSampleLogs();
  72. }
  73. ngAfterViewChecked() {
  74. if (this.shouldScroll) {
  75. this.scrollToBottom();
  76. this.shouldScroll = false;
  77. }
  78. }
  79. ngOnDestroy() {
  80. this.subscriptions.unsubscribe();
  81. }
  82. private addSampleLogs() {
  83. // 添加一些示例日志
  84. const sampleLogs: LogEntry[] = [
  85. {
  86. id: '1',
  87. timestamp: new Date(),
  88. level: 'info',
  89. message: '日志系统已初始化',
  90. source: 'log-service'
  91. },
  92. {
  93. id: '2',
  94. timestamp: new Date(Date.now() - 60000),
  95. level: 'info',
  96. message: '连接到 opencode 服务',
  97. source: 'opencode-client'
  98. },
  99. {
  100. id: '3',
  101. timestamp: new Date(Date.now() - 30000),
  102. level: 'debug',
  103. message: '用户登录成功',
  104. sessionID: 'session-123',
  105. source: 'auth-service'
  106. }
  107. ];
  108. this.allLogs = [...sampleLogs, ...this.allLogs];
  109. this.applyFilters();
  110. }
  111. applyFilters() {
  112. this.filteredLogs = this.allLogs.filter(log => {
  113. // 级别过滤
  114. if (this.selectedLevel !== 'all' && log.level !== this.selectedLevel) {
  115. return false;
  116. }
  117. // 会话过滤
  118. if (this.selectedSessionId !== 'all') {
  119. if (!log.sessionID) return false;
  120. if (log.sessionID !== this.selectedSessionId) return false;
  121. }
  122. // 实例过滤(如果设置了instanceId)
  123. return true;
  124. });
  125. // 按时间排序(最新的在前面)
  126. this.filteredLogs.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
  127. }
  128. getLevelLabel(level: string): string {
  129. const labels: { [key: string]: string } = {
  130. 'info': '信息',
  131. 'warn': '警告',
  132. 'error': '错误',
  133. 'debug': '调试'
  134. };
  135. return labels[level] || level;
  136. }
  137. getSessionTitle(sessionId: string): string {
  138. const session = this.availableSessions.find(s => s.id === sessionId);
  139. return session ? session.title : sessionId;
  140. }
  141. toggleAutoScroll() {
  142. this.autoScroll = !this.autoScroll;
  143. if (this.autoScroll) {
  144. this.shouldScroll = true;
  145. }
  146. }
  147. togglePause() {
  148. this.isPaused = !this.isPaused;
  149. }
  150. clearLogs() {
  151. this.allLogs = [];
  152. this.filteredLogs = [];
  153. }
  154. private scrollToBottom() {
  155. try {
  156. this.logContent.nativeElement.scrollTop =
  157. this.logContent.nativeElement.scrollHeight;
  158. } catch (err) {
  159. console.error('滚动失败:', err);
  160. }
  161. }
  162. }