+
+
-
-
Copilot
- -
+
+ -
- @for (comment of responses$ | async; track comment) { -
-
-
- {{ comment.date | date: 'sophisticatedDate' }} -
- - - -
- -
{{ comment.text }}
+
+
+ +
- } - +
+
Copilot
+ +
+ +
+ @for (comment of responses$ | async; track comment) { +
+
+
+ {{ comment.date | date: 'sophisticatedDate' }} +
+ + + +
+ +
{{ comment.text }}
+
+ } + + +
+
+
+ + + + + +
+
+ + +
+ + + +
+
+ +
+
+ + +
+
+
+
+ +
+
+

Rules Log

+ +
+ +
+
+
+ {{ msg.logLevel }} + {{ msg.parsedTimeStamp | date: 'short' }} +
+
+ {{ msg.message }} +
+
+ +
Tenant ID: {{ msg.tenantId }}
+
File ID: {{ msg.fileId }}
+
Dossier ID: {{ msg.dossierId }}
+
Rule Version: {{ msg.ruleVersion }}
+
Analysis Number: {{ msg.analysisNumber }}
+
Timestamp: {{ msg.parsedTimeStamp }}
+
+
- - - - - -
-
- - -
- - - -
-
- -
-
- - -
-
-
- - diff --git a/apps/red-ui/src/app/modules/admin/screens/rules/rules-screen/rules-screen.component.scss b/apps/red-ui/src/app/modules/admin/screens/rules/rules-screen/rules-screen.component.scss index 702bc576e..fa6ec1c96 100644 --- a/apps/red-ui/src/app/modules/admin/screens/rules/rules-screen/rules-screen.component.scss +++ b/apps/red-ui/src/app/modules/admin/screens/rules/rules-screen/rules-screen.component.scss @@ -96,3 +96,94 @@ ngx-monaco-editor { width: 100%; } } + +.rules-screen-container { + display: flex; + flex-direction: column; + height: 100vh; /* Adjust based on your layout */ + + .main-content { + flex: 1; + overflow: auto; + } + + .rules-logger { + flex-shrink: 0; + transition: max-height 0.3s ease; + overflow: hidden; + border-top: 1px solid #ccc; + background-color: #fafafa; + + &.collapsed { + max-height: 50px; /* Height of the header when collapsed */ + } + + &:not(.collapsed) { + max-height: 300px; /* Maximum height when expanded */ + } + + .logger-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 10px; + cursor: pointer; + + h3 { + margin: 0; + } + + .collapse-button { + background: none; + border: none; + cursor: pointer; + outline: none; + + mat-icon { + font-size: 24px; + } + } + } + + .logger-messages { + overflow-y: auto; + height: calc(100% - 50px); /* Subtract header height */ + padding: 10px; + + .log-entry { + padding: 10px; + border-bottom: 1px solid #eee; + + .log-header { + display: flex; + justify-content: space-between; + margin-bottom: 5px; + + .log-level { + font-weight: bold; + color: #007bff; + } + + .log-time { + font-size: 0.9em; + color: #666; + } + } + + .log-message { + margin-bottom: 5px; + font-size: 1em; + } + + .log-details { + font-size: 0.9em; + color: #666; + + div { + margin-bottom: 2px; + } + } + } + } + } +} diff --git a/apps/red-ui/src/app/modules/admin/screens/rules/rules-screen/rules-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/rules/rules-screen/rules-screen.component.ts index 46e233f0e..1cb2c8ee0 100644 --- a/apps/red-ui/src/app/modules/admin/screens/rules/rules-screen/rules-screen.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/rules/rules-screen/rules-screen.component.ts @@ -24,7 +24,8 @@ import { rulesScreenTranslations } from '../../../translations/rules-screen-tran import ICodeEditor = monaco.editor.ICodeEditor; import IModelDeltaDecoration = monaco.editor.IModelDeltaDecoration; import IStandaloneEditorConstructionOptions = monaco.editor.IStandaloneEditorConstructionOptions; -import { RulesLoggerComponent } from '@components/rules-logger/rules-logger.component'; +import { RulesLoggerService } from '@services/rules-logger.service'; +import { ScrollingModule } from '@angular/cdk/scrolling'; interface SyntaxError { line: number; @@ -39,6 +40,19 @@ interface UploadResponse { deprecatedWarnings: SyntaxError[]; } +interface RulesLogMessage { + tenantId: string; + fileId: string; + dossierId: string; + dossierTemplateId: string; + ruleVersion: number; + analysisNumber: number; + timeStamp: string; + logLevel: string; + message: string; + parsedTimeStamp?: Date; +} + const RULE_VALIDATION_TIMEOUT = 2000; @Component({ @@ -61,7 +75,7 @@ const RULE_VALIDATION_TIMEOUT = 2000; NamePipe, NgForOf, MatTooltip, - RulesLoggerComponent, + ScrollingModule, ], }) export default class RulesScreenComponent implements OnInit, ComponentCanDeactivate { @@ -71,6 +85,7 @@ export default class RulesScreenComponent implements OnInit, ComponentCanDeactiv readonly #errors = signal([]); #ruleValidationTimeout: number = null; readonly #copilotService = inject(CopilotService); + readonly #rulesLoggerService = inject(RulesLoggerService); readonly #currentUser = getCurrentUser(); protected readonly collapsed = signal(true); protected readonly IqserTooltipPositions = IqserTooltipPositions; @@ -88,10 +103,12 @@ export default class RulesScreenComponent implements OnInit, ComponentCanDeactiv initialLines: string[] = []; currentLines: string[] = []; isLeaving = false; + messages: RulesLogMessage[] = []; readonly type = input.required(); readonly numberOfErrors = computed(() => this.#errors().filter(e => !e.warning).length); readonly numberOfWarnings = computed(() => this.#errors().filter(e => e.warning).length); readonly responses$ = new BehaviorSubject<{ text: string; date: string }[]>([]); + isLogCollapsed = true; constructor( readonly permissionsService: PermissionsService, @@ -114,6 +131,15 @@ export default class RulesScreenComponent implements OnInit, ComponentCanDeactiv destination: '/app/rules-copilot', body: JSON.stringify({ prompts: ['manageradmin'] }), }); + + this.#rulesLoggerService + .listen('/topic/' + tenant + '/rule-log-events') + .pipe(takeUntilDestroyed()) + .subscribe(message => { + message.parsedTimeStamp = this.parseTimestamp(message.timeStamp); + this.messages.push(message); + this._changeDetectorRef.detectChanges(); + }); } set isLeavingPage(isLeaving: boolean) { @@ -352,4 +378,23 @@ export default class RulesScreenComponent implements OnInit, ComponentCanDeactiv () => this._loadingService.stop(), ); } + + parseTimestamp(timestamp: string): Date { + let adjustedTimestamp = timestamp.replace('Z', ''); + + const dotIndex = adjustedTimestamp.indexOf('.'); + if (dotIndex !== -1) { + adjustedTimestamp = adjustedTimestamp.substring(0, dotIndex); + } + + return new Date(adjustedTimestamp); + } + + trackByMessageId(index: number, msg: RulesLogMessage): string { + return msg.timeStamp; // Assuming timeStamp is unique + } + + toggleLogCollapse() { + this.isLogCollapsed = !this.isLogCollapsed; + } } diff --git a/apps/red-ui/src/app/modules/shared/components/editor/editor.component.html b/apps/red-ui/src/app/modules/shared/components/editor/editor.component.html index 0273a706b..5fa9ba7ee 100644 --- a/apps/red-ui/src/app/modules/shared/components/editor/editor.component.html +++ b/apps/red-ui/src/app/modules/shared/components/editor/editor.component.html @@ -8,5 +8,3 @@ > - - diff --git a/apps/red-ui/src/app/modules/shared/components/editor/editor.component.ts b/apps/red-ui/src/app/modules/shared/components/editor/editor.component.ts index 7cdb030e6..18338bd46 100644 --- a/apps/red-ui/src/app/modules/shared/components/editor/editor.component.ts +++ b/apps/red-ui/src/app/modules/shared/components/editor/editor.component.ts @@ -7,14 +7,13 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { List, OnChange } from '@iqser/common-ui/lib/utils'; import { MonacoEditorModule, MonacoStandaloneCodeEditor, MonacoStandaloneDiffEditor } from '@materia-ui/ngx-monaco-editor'; import { FormsModule } from '@angular/forms'; -import { NgIf } from '@angular/common'; import IStandaloneEditorConstructionOptions = monaco.editor.IStandaloneEditorConstructionOptions; import IDiffEditor = monaco.editor.IDiffEditor; import ICodeEditor = monaco.editor.ICodeEditor; import IModelDeltaDecoration = monaco.editor.IModelDeltaDecoration; import ILineChange = monaco.editor.ILineChange; import IEditorMouseEvent = monaco.editor.IEditorMouseEvent; -import { RulesLoggerComponent } from '@components/rules-logger/rules-logger.component'; +import { NgIf } from '@angular/common'; const MIN_WORD_LENGTH = 2; const lineChangeToDecoration = ({ originalEndLineNumber, originalStartLineNumber }: ILineChange) => @@ -33,7 +32,7 @@ const notZero = (lineChange: ILineChange) => lineChange.originalEndLineNumber != templateUrl: './editor.component.html', styleUrls: ['./editor.component.scss'], standalone: true, - imports: [MonacoEditorModule, FormsModule, NgIf, RulesLoggerComponent], + imports: [MonacoEditorModule, FormsModule, NgIf], }) export class EditorComponent implements OnInit, OnChanges { @Input() showDiffEditor = false;