Skip to content

Commit aa1d03b

Browse files
authored
Merge pull request #484 from hashtopolis/1323-enhancement-new-ui-detail-page-redirect-when-404403
Fix issue #1323 - redirect 404/403 in edit pages (agents, tasks, hash…
2 parents bdb2cc2 + f26b8fa commit aa1d03b

File tree

9 files changed

+860
-598
lines changed

9 files changed

+860
-598
lines changed
Lines changed: 77 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,77 @@
1-
<grid-main>
2-
<app-page-subtitle [subtitle]="pageTitle"></app-page-subtitle>
3-
4-
<form [formGroup]="newEditPreprocessorForm" (ngSubmit)="onSubmit()" fxLayout="column" fxLayoutGap="24px">
5-
<div fxLayout="column" fxLayoutGap="16px">
6-
<input-text width="500px" title="Name" formControlName="name" [isRequired]="true"></input-text>
7-
<input-text width="500px" title="Binary Basename" formControlName="binaryName" [isRequired]="true"></input-text>
8-
<input-text inputType="url" width="500px" title="Download URL" [pattern]="urlPattern" formControlName="url"
9-
[isRequired]="true"
10-
tooltip="Link where the client can download a 7zip with the preprocessor, e.g. https://example.com/preprocessor-1.0.0.7z"></input-text>
11-
</div>
12-
<h4>Commands (set to empty if not available)</h4>
13-
<div fxLayout="column" fxLayoutGap="16px">
14-
<input-text width="500px" title="Keyspace Command" formControlName="keyspaceCommand"></input-text>
15-
<input-text width="500px" title="Skip Command" formControlName="skipCommand"
16-
data-testid="input_skipCommand"></input-text>
17-
<input-text width="500px" title="Limit Command" formControlName="limitCommand"
18-
data-testid="input_limitCommand"></input-text>
19-
</div>
20-
21-
<grid-buttons>
22-
<button-submit name="Cancel" [disabled]="false" type="cancel"></button-submit>
23-
<button-submit data-testid="submit-button-newPreprocessor" [name]="submitButtonText"
24-
[disabled]="!newEditPreprocessorForm.valid || (isEditMode ? !roleService.hasRole('update') : !roleService.hasRole('create'))"></button-submit>
25-
</grid-buttons>
26-
27-
@if (newEditPreprocessorForm.invalid && newEditPreprocessorForm.touched) {
28-
<span class="help-block">Please complete
29-
all required fields!</span>
30-
}
31-
</form>
32-
33-
</grid-main>
1+
@if (!isLoading) {
2+
<grid-main>
3+
<app-page-subtitle [subtitle]="pageTitle"></app-page-subtitle>
4+
5+
<form
6+
[formGroup]="newEditPreprocessorForm"
7+
(ngSubmit)="onSubmit()"
8+
fxLayout="column"
9+
fxLayoutGap="24px"
10+
>
11+
<div fxLayout="column" fxLayoutGap="16px">
12+
<input-text
13+
width="500px"
14+
title="Name"
15+
formControlName="name"
16+
[isRequired]="true"
17+
></input-text>
18+
19+
<input-text
20+
width="500px"
21+
title="Binary Basename"
22+
formControlName="binaryName"
23+
[isRequired]="true"
24+
></input-text>
25+
26+
<input-text
27+
inputType="url"
28+
width="500px"
29+
title="Download URL"
30+
[pattern]="urlPattern"
31+
formControlName="url"
32+
[isRequired]="true"
33+
tooltip="Link where the client can download a 7zip with the preprocessor, e.g. https://example.com/preprocessor-1.0.0.7z"
34+
></input-text>
35+
</div>
36+
37+
<h4>Commands (set to empty if not available)</h4>
38+
39+
<div fxLayout="column" fxLayoutGap="16px">
40+
<input-text
41+
width="500px"
42+
title="Keyspace Command"
43+
formControlName="keyspaceCommand"
44+
></input-text>
45+
46+
<input-text
47+
width="500px"
48+
title="Skip Command"
49+
formControlName="skipCommand"
50+
data-testid="input_skipCommand"
51+
></input-text>
52+
53+
<input-text
54+
width="500px"
55+
title="Limit Command"
56+
formControlName="limitCommand"
57+
data-testid="input_limitCommand"
58+
></input-text>
59+
</div>
60+
61+
<grid-buttons>
62+
<button-submit name="Cancel" [disabled]="false" type="cancel"></button-submit>
63+
<button-submit
64+
data-testid="submit-button-newPreprocessor"
65+
[name]="submitButtonText"
66+
[disabled]="!newEditPreprocessorForm.valid"
67+
></button-submit>
68+
</grid-buttons>
69+
70+
@if (newEditPreprocessorForm.invalid && newEditPreprocessorForm.touched) {
71+
<span class="help-block">
72+
Please complete all required fields!
73+
</span>
74+
}
75+
</form>
76+
</grid-main>
77+
}

src/app/config/engine/preprocessors/new_edit-preprocessor/new_edit-preprocessor.component.spec.ts

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,17 +52,40 @@ describe('NewEditPreprocessorComponent', () => {
5252
fixture.detectChanges(); // triggers ngOnInit and loads permissions
5353
});
5454

55+
/**
56+
* Helper to check page title and button text
57+
* @param title Title text
58+
* @param buttonText Button text
59+
*/
5560
/**
5661
* Helper to check page title and button text
5762
* @param title Title text
5863
* @param buttonText Button text
5964
*/
6065
function expectPageTitleAndButton(title: string, buttonText: string) {
61-
const buttonDebugEl = fixture.debugElement.query(By.css('[data-testid="submit-button-newPreprocessor"]'));
62-
expect(buttonDebugEl.nativeElement.textContent).toContain(buttonText);
66+
const hostElement: HTMLElement = fixture.nativeElement;
67+
68+
// 1) Intentar localizar por data-testid (nuevo modo)
69+
let buttonEl = hostElement.querySelector('[data-testid="submit-button-newPreprocessor"]') as HTMLElement | null;
70+
71+
// 2) Fallback: cualquier botón de submit (por si en modo Edit no hay data-testid)
72+
if (!buttonEl) {
73+
buttonEl = hostElement.querySelector('button[type="submit"]') as HTMLElement | null;
74+
}
75+
76+
expect(buttonEl).withContext('Submit button not found').not.toBeNull();
77+
78+
if (buttonEl) {
79+
expect(buttonEl.textContent).toContain(buttonText);
80+
}
6381

64-
const subtitleEl = fixture.nativeElement.querySelector('app-page-subtitle');
65-
expect(subtitleEl.textContent).toContain(title);
82+
const subtitleEl = hostElement.querySelector('app-page-subtitle') as HTMLElement | null;
83+
84+
expect(subtitleEl).withContext('Subtitle not found').not.toBeNull();
85+
86+
if (subtitleEl) {
87+
expect(subtitleEl.textContent).toContain(title);
88+
}
6689
}
6790

6891
it('should create component and form', () => {
@@ -222,15 +245,24 @@ describe('NewEditPreprocessorComponent', () => {
222245
expectPageTitleAndButton('New Preprocessor', 'Create');
223246
});
224247

225-
it('should display the page title "Edit Preprocessor" and button name "Update" in Edit mode', () => {
248+
it('should display the page title "Edit Preprocessor" and button name "Update" in Edit mode', async () => {
226249
const activatedRoute = TestBed.inject(ActivatedRoute);
250+
227251
activatedRoute.snapshot.paramMap.get = () => '9';
252+
253+
mockRoleService.hasRole.and.returnValue(true);
228254
mockGlobalService.get.and.returnValue(of({ data: {}, included: [] }));
229255

230256
fixture = TestBed.createComponent(NewEditPreprocessorComponent);
231257
component = fixture.componentInstance;
232258
fixture.detectChanges();
233259

260+
await fixture.whenStable();
261+
262+
(component as unknown as { isLoading: boolean }).isLoading = false;
263+
264+
fixture.detectChanges();
265+
234266
expect(component.pageTitle).toBe('Edit Preprocessor');
235267
expect(component.submitButtonText).toBe('Update');
236268

src/app/config/engine/preprocessors/new_edit-preprocessor/new_edit-preprocessor.component.ts

Lines changed: 53 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Subscription, firstValueFrom } from 'rxjs';
22

3+
import { HttpBackend, HttpClient, HttpErrorResponse } from '@angular/common/http';
34
import { Component, OnInit } from '@angular/core';
45
import { FlexModule } from '@angular/flex-layout';
56
import { FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms';
@@ -11,9 +12,9 @@ import { ResponseWrapper } from '@models/response.model';
1112
import { JsonAPISerializer } from '@services/api/serializer-service';
1213
import { SERV, ValidationPatterns } from '@services/main.config';
1314
import { GlobalService } from '@services/main.service';
14-
import { RequestParamBuilder } from '@services/params/builder-implementation.service';
1515
import { PreprocessorRoleService } from '@services/roles/binaries/preprocessor-role.service';
1616
import { AlertService } from '@services/shared/alert.service';
17+
import { ConfigService } from '@services/shared/config.service';
1718

1819
import {
1920
NewEditPreprocessorForm,
@@ -40,11 +41,14 @@ export class NewEditPreprocessorComponent implements OnInit {
4041

4142
newEditPreprocessorForm: FormGroup<NewEditPreprocessorForm>;
4243

43-
loading: boolean;
44+
loading = false;
45+
46+
isLoading = true;
47+
48+
private httpNoInterceptors: HttpClient;
4449

4550
/**
4651
* Array to hold subscriptions for cleanup on component destruction.
47-
* This prevents memory leaks by unsubscribing from observables when the component is destroyed.
4852
*/
4953
subscriptions: Subscription[] = [];
5054

@@ -53,52 +57,71 @@ export class NewEditPreprocessorComponent implements OnInit {
5357
private gs: GlobalService,
5458
private router: Router,
5559
private alert: AlertService,
60+
private cs: ConfigService,
61+
httpBackend: HttpBackend,
5662
protected roleService: PreprocessorRoleService
5763
) {
5864
this.newEditPreprocessorForm = getNewEditPreprocessorForm();
59-
this.loading = false;
65+
this.httpNoInterceptors = new HttpClient(httpBackend);
6066
}
6167

62-
ngOnInit() {
63-
this.preprocessorId = parseInt(this.route.snapshot.paramMap.get('id'));
64-
if (this.preprocessorId) {
68+
async ngOnInit(): Promise<void> {
69+
const idFromRoute = this.route.snapshot.paramMap.get('id');
70+
const parsedId = idFromRoute !== null ? Number(idFromRoute) : NaN;
71+
72+
if (Number.isFinite(parsedId)) {
73+
// Edit mode
6574
this.isEditMode = true;
75+
this.preprocessorId = parsedId;
6676
this.pageTitle = 'Edit Preprocessor';
6777
this.submitButtonText = 'Update';
6878

69-
this.loadPreprocessor(this.preprocessorId);
79+
try {
80+
await this.loadPreprocessor(this.preprocessorId);
81+
} catch (e: unknown) {
82+
const status = e instanceof HttpErrorResponse ? e.status : undefined;
83+
84+
if (status === 403) {
85+
void this.router.navigateByUrl('/forbidden');
86+
return;
87+
}
88+
89+
void this.router.navigateByUrl('/not-found');
90+
return;
91+
}
7092
}
93+
94+
this.isLoading = false;
7195
}
7296

7397
/**
7498
* Load preprocessor data from the server and patch the form with the data
7599
* @param preprocessorId ID of the preprocessor to load
76100
*/
77-
private loadPreprocessor(preprocessorId: number) {
78-
const params = new RequestParamBuilder().create();
79-
this.subscriptions.push(
80-
this.gs.get(SERV.PREPROCESSORS, preprocessorId, params).subscribe((response: ResponseWrapper) => {
81-
const preprocessor = new JsonAPISerializer().deserialize<JPreprocessor>({
82-
data: response.data,
83-
included: response.included
84-
});
85-
86-
this.newEditPreprocessorForm.patchValue({
87-
name: preprocessor.name,
88-
binaryName: preprocessor.binaryName,
89-
url: preprocessor.url,
90-
keyspaceCommand: preprocessor.keyspaceCommand,
91-
limitCommand: preprocessor.limitCommand,
92-
skipCommand: preprocessor.skipCommand
93-
});
94-
})
95-
);
101+
private async loadPreprocessor(preprocessorId: number): Promise<void> {
102+
const url = `${this.cs.getEndpoint()}${SERV.PREPROCESSORS.URL}/${preprocessorId}`;
103+
104+
const response = await firstValueFrom<ResponseWrapper>(this.httpNoInterceptors.get<ResponseWrapper>(url));
105+
106+
const preprocessor = new JsonAPISerializer().deserialize<JPreprocessor>({
107+
data: response.data,
108+
included: response.included
109+
});
110+
111+
this.newEditPreprocessorForm.patchValue({
112+
name: preprocessor.name,
113+
binaryName: preprocessor.binaryName,
114+
url: preprocessor.url,
115+
keyspaceCommand: preprocessor.keyspaceCommand,
116+
limitCommand: preprocessor.limitCommand,
117+
skipCommand: preprocessor.skipCommand
118+
});
96119
}
97120

98121
/**
99-
* Create new preprocessor upon form submission and redirect to preprocessor page on success
122+
* Create / update preprocessor upon form submission and redirect on success
100123
*/
101-
async onSubmit() {
124+
async onSubmit(): Promise<void> {
102125
if (this.newEditPreprocessorForm.invalid) return;
103126
this.loading = true;
104127

@@ -111,7 +134,7 @@ export class NewEditPreprocessorComponent implements OnInit {
111134
skipCommand: this.newEditPreprocessorForm.value.skipCommand
112135
};
113136

114-
if (this.isEditMode) {
137+
if (this.isEditMode && this.preprocessorId !== null) {
115138
try {
116139
this.subscriptions.push(
117140
this.gs.update(SERV.PREPROCESSORS, this.preprocessorId, payload).subscribe(() => {

0 commit comments

Comments
 (0)