-
- Generate a voucher to register the agent. Note that once the voucher
- is used it will be automatically deleted.
-
-
+ @if (!allowMultiVoucher) {
+
+ Generate a voucher to register the agent. Note that once the voucher
+ is used it will be automatically deleted.
+
+ } @else {
Generate vouchers to register agents. Vouchers remain available when the
"Register Multiple Agents Using Voucher(s)" option is enabled.
-
+ }
diff --git a/src/app/agents/new-agent/new-agent.component.spec.ts b/src/app/agents/new-agent/new-agent.component.spec.ts
index fae8cf096..8dbd74db7 100644
--- a/src/app/agents/new-agent/new-agent.component.spec.ts
+++ b/src/app/agents/new-agent/new-agent.component.spec.ts
@@ -1,3 +1,4 @@
+import { Perm } from '@constants/userpermissions.config';
import { of } from 'rxjs';
import { Clipboard } from '@angular/cdk/clipboard';
@@ -13,6 +14,7 @@ import { Router } from '@angular/router';
import { SERV } from '@services/main.config';
import { GlobalService } from '@services/main.service';
+import { PermissionService } from '@services/permission/permission.service';
import { AlertService } from '@services/shared/alert.service';
import { ConfigService } from '@services/shared/config.service';
@@ -28,7 +30,7 @@ import { TableModule } from '@src/app/shared/table/table-actions.module';
standalone: false
})
export class MockAgentBinariesTableComponent {
- @Input() isSelectable: boolean;
+ @Input() isSelectable: boolean = false;
}
// Voucher table mock
@@ -48,6 +50,7 @@ describe('NewAgentComponent', () => {
let alertServiceSpy: jasmine.SpyObj
;
let configServiceSpy: jasmine.SpyObj;
let globalServiceSpy: jasmine.SpyObj;
+ let permissionServiceSpy: jasmine.SpyObj;
let routerSpy: jasmine.SpyObj;
beforeEach(async () => {
@@ -55,6 +58,8 @@ describe('NewAgentComponent', () => {
alertServiceSpy = jasmine.createSpyObj('AlertService', ['showSuccessMessage']);
configServiceSpy = jasmine.createSpyObj('ConfigService', ['getEndpoint']);
globalServiceSpy = jasmine.createSpyObj('GlobalService', ['create', 'getAll']);
+ permissionServiceSpy = jasmine.createSpyObj('PermissionService', ['hasPermissionSync']);
+ permissionServiceSpy.hasPermissionSync.and.returnValue(true);
routerSpy = jasmine.createSpyObj('Router', ['navigate']);
// Provide default stub for configService.getEndpoint()
@@ -80,6 +85,7 @@ describe('NewAgentComponent', () => {
{ provide: AlertService, useValue: alertServiceSpy },
{ provide: ConfigService, useValue: configServiceSpy },
{ provide: GlobalService, useValue: globalServiceSpy },
+ { provide: PermissionService, useValue: permissionServiceSpy },
{ provide: Router, useValue: routerSpy }
]
}).compileComponents();
@@ -175,4 +181,43 @@ describe('NewAgentComponent', () => {
component.ngOnDestroy();
expect(component.newVoucherSubscription.unsubscribe).toHaveBeenCalled();
});
+
+ it('should allow agent creation for a user without AccessGroup.READ permission', fakeAsync(() => {
+ // Simulate a user who lacks AccessGroup.READ but retains Agent.CREATE, Agent.READ and Voucher.READ
+ // This is the exact scenario described in issue #1955
+ permissionServiceSpy.hasPermissionSync.and.callFake((perm: string) => perm !== Perm.GroupAccess.READ);
+
+ fixture = TestBed.createComponent(NewAgentComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+
+ // Component initializes without errors
+ expect(component).toBeTruthy();
+
+ // User can still create a voucher — the actual agent registration action
+ const voucher = 'fy7vjq56';
+ component.form.controls['voucher'].setValue(voucher);
+ component.table = jasmine.createSpyObj('VouchersTableComponent', ['reload']);
+ globalServiceSpy.create.and.returnValue(of({}));
+
+ component.onSubmit();
+ tick();
+
+ expect(globalServiceSpy.create).toHaveBeenCalledWith(SERV.VOUCHER, { voucher: voucher });
+ expect(alertServiceSpy.showSuccessMessage).toHaveBeenCalledWith('New voucher successfully created!');
+ }));
+
+ it('should not make any access group API call during agent creation', () => {
+ // Agent creation does not require or fetch access groups — verifies no extraneous dependency
+ const calledEndpoints = globalServiceSpy.getAll.calls.allArgs().map((args: unknown[]) => args[0]);
+ expect(calledEndpoints).not.toContain(SERV.ACCESS_GROUPS);
+ });
+
+ it('should hide agent binaries table and avoid 403 when user lacks AgentBinary.READ permission', () => {
+ // Secondary fix: users without AgentBinary.READ won't trigger a 403 on the binaries endpoint
+ component.canReadAgentBinaries = false;
+ fixture.detectChanges();
+ const table = fixture.nativeElement.querySelector('app-agent-binaries-table');
+ expect(table).toBeNull();
+ });
});
diff --git a/src/app/agents/new-agent/new-agent.component.ts b/src/app/agents/new-agent/new-agent.component.ts
index 7ef27ece0..4cad8bc4b 100644
--- a/src/app/agents/new-agent/new-agent.component.ts
+++ b/src/app/agents/new-agent/new-agent.component.ts
@@ -1,3 +1,4 @@
+import { Perm } from '@constants/userpermissions.config';
import { Subscription } from 'rxjs';
import { VouchersTableComponent } from 'src/app/core/_components/tables/vouchers-table/vouchers-table.component';
import { GlobalService } from 'src/app/core/_services/main.service';
@@ -10,6 +11,7 @@ import { FormControl, FormGroup } from '@angular/forms';
import { Router } from '@angular/router';
import { SERV } from '@services/main.config';
+import { PermissionService } from '@services/permission/permission.service';
import { AlertService } from '@services/shared/alert.service';
import { VoucherForm } from '@src/app/agents/new-agent/new-agent.form';
@@ -30,6 +32,7 @@ export class NewAgentComponent implements OnInit, OnDestroy {
private alertService = inject(AlertService);
private cs = inject(ConfigService);
private gs = inject(GlobalService);
+ private permissionService = inject(PermissionService);
private router = inject(Router);
form: FormGroup = new FormGroup({
@@ -38,6 +41,7 @@ export class NewAgentComponent implements OnInit, OnDestroy {
agentURL: string;
newVoucherSubscription: Subscription;
allowMultiVoucher = false;
+ canReadAgentBinaries = this.permissionService.hasPermissionSync(Perm.AgentBinary.READ);
@ViewChild('table') table: VouchersTableComponent;
diff --git a/src/app/core/_services/main.service.ts b/src/app/core/_services/main.service.ts
index c79cb973d..e63b10846 100644
--- a/src/app/core/_services/main.service.ts
+++ b/src/app/core/_services/main.service.ts
@@ -276,9 +276,14 @@ export class GlobalService {
.pipe(debounceTime(2000));
}
- getRelationships(serviceConfig: ServiceConfig, id: number, relType: string): Observable {
+ getRelationships(
+ serviceConfig: ServiceConfig,
+ id: number,
+ relType: string,
+ options?: { headers?: HttpHeaders }
+ ): Observable {
return this.http
- .get(this.cs.getEndpoint() + serviceConfig.URL + '/' + id + '/' + relType)
+ .get(this.cs.getEndpoint() + serviceConfig.URL + '/' + id + '/' + relType, options)
.pipe(debounceTime(2000));
}
diff --git a/src/app/core/_services/roles/agents/agent-role.service.ts b/src/app/core/_services/roles/agents/agent-role.service.ts
index 5f6dc22bf..c94ed2a05 100644
--- a/src/app/core/_services/roles/agents/agent-role.service.ts
+++ b/src/app/core/_services/roles/agents/agent-role.service.ts
@@ -20,7 +20,7 @@ export class AgentRoleService extends RoleService {
readChunk: [Perm.Task.READ, Perm.TaskWrapper.READ, Perm.Chunk.READ],
readAccessGroup: [Perm.GroupAccess.READ],
readError: [Perm.AgentError.READ],
- create: [Perm.Agent.CREATE, Perm.Agent.READ, Perm.Voucher.READ, Perm.AgentBinary.READ],
+ create: [Perm.Agent.CREATE, Perm.Agent.READ, Perm.Voucher.READ],
update: [Perm.Agent.UPDATE],
updateAssignment: [Perm.AgentAssignment.UPDATE, Perm.AgentAssignment.READ, Perm.Task.READ, Perm.TaskWrapper.READ]
});
diff --git a/src/app/core/_services/roles/hashlists/hashlist-role.service.ts b/src/app/core/_services/roles/hashlists/hashlist-role.service.ts
index 15f8a6aa2..5eddaea6b 100644
--- a/src/app/core/_services/roles/hashlists/hashlist-role.service.ts
+++ b/src/app/core/_services/roles/hashlists/hashlist-role.service.ts
@@ -14,7 +14,7 @@ import { RoleService } from '@services/roles/base/role.service';
export class HashListRoleService extends RoleService {
constructor(permissionService: PermissionService) {
super(permissionService, {
- create: [Perm.Hashlist.CREATE, Perm.Hashtype.READ, Perm.GroupAccess.READ],
+ create: [Perm.Hashlist.CREATE, Perm.Hashtype.READ],
read: [Perm.Hashlist.READ],
update: [Perm.Hashlist.UPDATE],
tasks: [Perm.TaskWrapper.READ],
diff --git a/src/app/files/new-files/new-files.component.spec.ts b/src/app/files/new-files/new-files.component.spec.ts
index c14bb8097..4bda18a62 100644
--- a/src/app/files/new-files/new-files.component.spec.ts
+++ b/src/app/files/new-files/new-files.component.spec.ts
@@ -1,5 +1,6 @@
-import { of } from 'rxjs';
+import { of, throwError } from 'rxjs';
+import { HttpHeaders } from '@angular/common/http';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ReactiveFormsModule } from '@angular/forms';
import { MatIconModule } from '@angular/material/icon';
@@ -205,17 +206,60 @@ describe('NewFilesComponent', () => {
});
describe('Access group scoping', () => {
- it('should fetch access groups via getRelationships for the current user, not getAll', () => {
+ it('should fetch access groups via getRelationships with X-Skip-Error-Dialog header', () => {
setup('wordlist-new');
const gs = TestBed.inject(GlobalService) as unknown as MockGlobalService;
- // Component must use getRelationships to get user-scoped access groups
- expect(gs.getRelationships).toHaveBeenCalledWith(SERV.USERS, 1, RelationshipType.ACCESSGROUPS);
+ const callArgs = gs.getRelationships.calls.mostRecent().args;
+ expect(callArgs[0]).toEqual(SERV.USERS);
+ expect(callArgs[1]).toBe(1);
+ expect(callArgs[2]).toBe(RelationshipType.ACCESSGROUPS);
+ expect(callArgs[3]).toBeDefined();
+ expect(callArgs[3].headers).toBeInstanceOf(HttpHeaders);
+ expect(callArgs[3].headers.get('X-Skip-Error-Dialog')).toBe('true');
// getAll must NOT be called — the component should not fetch all access groups
expect(gs.getAll).not.toHaveBeenCalled();
});
+ it('should fall back to default access group when getRelationships returns error (403)', async () => {
+ TestBed.overrideProvider(GlobalService, {
+ useValue: {
+ ...new MockGlobalService(),
+ getRelationships: jasmine
+ .createSpy('getRelationships')
+ .and.returnValue(throwError(() => new Error('403 Forbidden'))),
+ userId: 1
+ }
+ });
+ TestBed.overrideProvider(ActivatedRoute, {
+ useValue: { data: of({ kind: 'wordlist-new' }) }
+ });
+
+ fixture = TestBed.createComponent(NewFilesComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ await fixture.whenStable();
+
+ expect(component.selectAccessgroup.length).toBe(1);
+ expect(component.selectAccessgroup[0]).toEqual({ id: '1', name: 'Default' });
+ expect(component.form.get('accessGroupId').value).toBe(1);
+ expect(component.form.get('accessGroupId').disabled).toBeTrue();
+ expect(component.isLoading).toBeFalse();
+ });
+
+ it('should fall back to default access group when response has empty data', async () => {
+ // Default mock already returns { data: [], included: [] } → empty → triggers fallback
+ setup('wordlist-new');
+ await fixture.whenStable();
+
+ expect(component.selectAccessgroup.length).toBe(1);
+ expect(component.selectAccessgroup[0]).toEqual({ id: '1', name: 'Default' });
+ expect(component.form.get('accessGroupId').value).toBe(1);
+ expect(component.form.get('accessGroupId').disabled).toBeTrue();
+ expect(component.isLoading).toBeFalse();
+ });
+
it('should correctly transform access group API data to select options', () => {
// Simulate deserialized access groups (what JsonAPISerializer would produce)
const deserialized = [
diff --git a/src/app/files/new-files/new-files.component.ts b/src/app/files/new-files/new-files.component.ts
index 740918cfa..a240876b9 100644
--- a/src/app/files/new-files/new-files.component.ts
+++ b/src/app/files/new-files/new-files.component.ts
@@ -1,7 +1,8 @@
import { Subject, firstValueFrom, takeUntil } from 'rxjs';
+import { HttpHeaders } from '@angular/common/http';
import { ChangeDetectorRef, Component, OnDestroy, OnInit, inject } from '@angular/core';
-import { FormGroup, Validators } from '@angular/forms';
+import { FormGroup } from '@angular/forms';
import { MatCheckboxChange } from '@angular/material/checkbox';
import { MatDialog } from '@angular/material/dialog';
import { ActivatedRoute, Router } from '@angular/router';
@@ -150,27 +151,6 @@ export class NewFilesComponent implements OnInit, OnDestroy {
buildForm() {
this.form = getNewFilesForm();
this.form.patchValue({ fileType: this.filterType });
- this.updateValidatorsBySourceType(this.form.get('sourceType').value);
- }
-
- private updateValidatorsBySourceType(sourceType: string): void {
- const filenameCtrl = this.form.get('filename');
- const urlCtrl = this.form.get('url');
-
- if (!filenameCtrl || !urlCtrl) {
- return;
- }
-
- if (sourceType === 'url') {
- filenameCtrl.setValidators([Validators.required]);
- urlCtrl.setValidators([Validators.required]);
- } else {
- filenameCtrl.clearValidators();
- urlCtrl.clearValidators();
- }
-
- filenameCtrl.updateValueAndValidity({ emitEvent: false });
- urlCtrl.updateValueAndValidity({ emitEvent: false });
}
/**
@@ -178,10 +158,11 @@ export class NewFilesComponent implements OnInit, OnDestroy {
*/
async loadData() {
this.isLoading = true;
+ const skipErrorHeaders = { headers: new HttpHeaders({ 'X-Skip-Error-Dialog': 'true' }) };
try {
const response: ResponseWrapper = await firstValueFrom(
- this.gs.getRelationships(SERV.USERS, this.gs.userId, RelationshipType.ACCESSGROUPS)
+ this.gs.getRelationships(SERV.USERS, this.gs.userId, RelationshipType.ACCESSGROUPS, skipErrorHeaders)
);
const accessGroups = new JsonAPISerializer().deserialize({
@@ -190,14 +171,23 @@ export class NewFilesComponent implements OnInit, OnDestroy {
});
this.selectAccessgroup = transformSelectOptions(accessGroups, ACCESS_GROUP_FIELD_MAPPING);
- } catch (error) {
- console.error('Error fetching access groups:', error);
+ if (!this.selectAccessgroup || this.selectAccessgroup.length === 0) {
+ this.setDefaultAccessGroup();
+ }
+ } catch {
+ this.setDefaultAccessGroup();
} finally {
this.isLoading = false;
this.changeDetectorRef.detectChanges();
}
}
+ private setDefaultAccessGroup(): void {
+ this.selectAccessgroup = [{ id: '1', name: 'Default' }];
+ this.form.patchValue({ accessGroupId: 1 });
+ this.form.get('accessGroupId').disable();
+ }
+
/**
* Loads the list of files available on the server in the import directory.
*/
@@ -223,7 +213,7 @@ export class NewFilesComponent implements OnInit, OnDestroy {
*/
async onSubmit(): Promise {
if (this.form.valid && !this.submitted) {
- const form = this.onBeforeSubmit(this.form.value, false);
+ const form = this.onBeforeSubmit(this.form.getRawValue(), false);
this.isCreatingLoading = true;
this.submitted = true;
@@ -233,7 +223,7 @@ export class NewFilesComponent implements OnInit, OnDestroy {
await firstValueFrom(this.gs.create(SERV.FILES, form.update));
// After successful creation, update form and show alert
- this.onBeforeSubmit(this.form.value, true);
+ this.onBeforeSubmit(this.form.getRawValue(), true);
this.alert.showSuccessMessage('New File created');
this.isCreatingLoading = false;
this.submitted = false;
@@ -299,7 +289,6 @@ export class NewFilesComponent implements OnInit, OnDestroy {
sourceType: type,
sourceData: ''
});
- this.updateValidatorsBySourceType(type);
// Load server import directory files only when switching to tab3
if (view === 'tab3' && this.serverFiles.length === 0) {
@@ -338,50 +327,22 @@ export class NewFilesComponent implements OnInit, OnDestroy {
this.alert.showErrorMessage('Please select a file to upload.');
return;
}
- const form = this.onBeforeSubmit(this.form.value, false);
+ const form = this.onBeforeSubmit(this.form.getRawValue(), false);
this.isCreatingLoading = true;
for (let i = 0; i < files.length; i++) {
this.uploadService
.uploadFile(files[0], files[0].name, SERV.FILES, form.update, ['/files', this.redirect])
.pipe(takeUntil(this.fileUnsubscribe))
- .subscribe({
- next: (progress) => {
- this.uploadProgress = progress;
- this.changeDetectorRef.detectChanges();
- if (this.uploadProgress === 100) {
- this.isCreatingLoading = false;
- }
- },
- error: (error) => {
- this.uploadProgress = 0;
+ .subscribe((progress) => {
+ this.uploadProgress = progress;
+ this.changeDetectorRef.detectChanges();
+ if (this.uploadProgress === 100) {
this.isCreatingLoading = false;
- this.alert.showErrorMessage(this.buildUploadErrorMessage(error));
- this.changeDetectorRef.detectChanges();
}
});
}
}
- private buildUploadErrorMessage(error: unknown): string {
- if (typeof error === 'string') {
- return `Failed to upload file: ${error}`;
- }
-
- if (error && typeof error === 'object') {
- const errorObject = error as { error?: { title?: string; message?: string }; message?: string };
- const backendMessage = errorObject.error?.title || errorObject.error?.message;
- if (backendMessage) {
- return `Failed to upload file: ${backendMessage}`;
- }
-
- if (errorObject.message) {
- return `Failed to upload file: ${errorObject.message}`;
- }
- }
-
- return 'Failed to upload file.';
- }
-
/**
* Handles the submission of selected server files for import.
* @protected
@@ -389,7 +350,6 @@ export class NewFilesComponent implements OnInit, OnDestroy {
protected async onSubmitServerImport() {
if (this.selectedServerFiles.size === 0) return;
if (this.submitted) return;
- if (this.form.invalid) return;
this.isCreatingLoading = true;
this.submitted = true;
@@ -399,9 +359,9 @@ export class NewFilesComponent implements OnInit, OnDestroy {
// Build form data for each server file
const payload = {
filename: file,
- isSecret: this.form.value.isSecret || false,
+ isSecret: this.form.getRawValue().isSecret || false,
fileType: this.filterType,
- accessGroupId: this.form.value.accessGroupId,
+ accessGroupId: this.form.getRawValue().accessGroupId,
sourceType: 'import', // IMPORTANT: tells backend to pick it from server import dir
sourceData: file // some backends require this too
};
@@ -414,9 +374,7 @@ export class NewFilesComponent implements OnInit, OnDestroy {
await Promise.all(requests);
this.alert.showSuccessMessage('Server files imported successfully!');
- // Reload server files
- await this.loadServerFiles();
- this.selectedServerFiles.clear();
+ void this.router.navigate(['/files', this.redirect]);
} catch (error) {
console.error('Error importing server files:', error);
this.alert.showErrorMessage('Could not import selected files.');
diff --git a/src/app/hashlists/new-hashlist/new-hashlist.component.spec.ts b/src/app/hashlists/new-hashlist/new-hashlist.component.spec.ts
index 38715e627..0f33da641 100644
--- a/src/app/hashlists/new-hashlist/new-hashlist.component.spec.ts
+++ b/src/app/hashlists/new-hashlist/new-hashlist.component.spec.ts
@@ -1,5 +1,6 @@
-import { of } from 'rxjs';
+import { of, throwError } from 'rxjs';
+import { HttpHeaders } from '@angular/common/http';
import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';
import { ReactiveFormsModule } from '@angular/forms';
import { MatDialog, MatDialogRef } from '@angular/material/dialog';
@@ -245,7 +246,7 @@ describe('NewHashlistComponent', () => {
tick();
const expectedPayload = {
- ...component.form.value,
+ ...component.form.getRawValue(),
sourceType: 'import',
sourceData: 'hashes.txt'
};
@@ -321,9 +322,45 @@ describe('NewHashlistComponent', () => {
});
describe('Access group scoping', () => {
- it('should fetch access groups via getRelationships for the current user, not getAll', () => {
- expect(gsSpy.getRelationships).toHaveBeenCalledWith(SERV.USERS, 1, RelationshipType.ACCESSGROUPS);
+ it('should fetch access groups via getRelationships with X-Skip-Error-Dialog header', () => {
+ const callArgs = gsSpy.getRelationships.calls.mostRecent().args;
+ expect(callArgs[0]).toEqual(SERV.USERS);
+ expect(callArgs[1]).toBe(1);
+ expect(callArgs[2]).toBe(RelationshipType.ACCESSGROUPS);
+ expect(callArgs[3]).toBeDefined();
+ expect(callArgs[3].headers).toBeInstanceOf(HttpHeaders);
+ expect(callArgs[3].headers.get('X-Skip-Error-Dialog')).toBe('true');
+
+ // getAll must NOT be called for access groups
expect(gsSpy.getAll).not.toHaveBeenCalledWith(SERV.ACCESS_GROUPS);
});
+
+ it('should fall back to default access group when getRelationships returns error (403)', () => {
+ // Re-create component with error response
+ gsSpy.getRelationships.and.returnValue(throwError(() => new Error('403 Forbidden')));
+ fixture = TestBed.createComponent(NewHashlistComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+
+ expect(component.selectAccessgroup.length).toBe(1);
+ expect(component.selectAccessgroup[0]).toEqual({ id: '1', name: 'Default' });
+ expect(component.form.get('accessGroupId').value).toBe(1);
+ expect(component.form.get('accessGroupId').disabled).toBeTrue();
+ expect(component.isLoadingAccessGroups).toBeFalse();
+ });
+
+ it('should fall back to default access group when response has empty data', () => {
+ // Re-create component with empty access groups
+ gsSpy.getRelationships.and.returnValue(of({ data: [], included: [] }));
+ fixture = TestBed.createComponent(NewHashlistComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+
+ expect(component.selectAccessgroup.length).toBe(1);
+ expect(component.selectAccessgroup[0]).toEqual({ id: '1', name: 'Default' });
+ expect(component.form.get('accessGroupId').value).toBe(1);
+ expect(component.form.get('accessGroupId').disabled).toBeTrue();
+ expect(component.isLoadingAccessGroups).toBeFalse();
+ });
});
});
diff --git a/src/app/hashlists/new-hashlist/new-hashlist.component.ts b/src/app/hashlists/new-hashlist/new-hashlist.component.ts
index ec1f8a107..4230688a7 100644
--- a/src/app/hashlists/new-hashlist/new-hashlist.component.ts
+++ b/src/app/hashlists/new-hashlist/new-hashlist.component.ts
@@ -3,6 +3,7 @@
*/
import { Subject, Subscription, firstValueFrom, takeUntil } from 'rxjs';
+import { HttpHeaders } from '@angular/common/http';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit, inject } from '@angular/core';
import { FormGroup } from '@angular/forms';
import { MatDialog } from '@angular/material/dialog';
@@ -164,16 +165,27 @@ export class NewHashlistComponent implements OnInit, OnDestroy {
*/
loadData(): void {
this.loadConfigs();
+ const skipErrorHeaders = { headers: new HttpHeaders({ 'X-Skip-Error-Dialog': 'true' }) };
const accessGroupSubscription = this.gs
- .getRelationships(SERV.USERS, this.gs.userId, RelationshipType.ACCESSGROUPS)
- .subscribe((response: ResponseWrapper) => {
- const accessGroups = new JsonAPISerializer().deserialize({
- data: response.data,
- included: response.included
- });
- this.selectAccessgroup = transformSelectOptions(accessGroups, ACCESS_GROUP_FIELD_MAPPING);
- this.isLoadingAccessGroups = false;
- this.changeDetectorRef.detectChanges();
+ .getRelationships(SERV.USERS, this.gs.userId, RelationshipType.ACCESSGROUPS, skipErrorHeaders)
+ .subscribe({
+ next: (response: ResponseWrapper) => {
+ const accessGroups = new JsonAPISerializer().deserialize({
+ data: response.data,
+ included: response.included
+ });
+ this.selectAccessgroup = transformSelectOptions(accessGroups, ACCESS_GROUP_FIELD_MAPPING);
+ if (!this.selectAccessgroup || this.selectAccessgroup.length === 0) {
+ this.setDefaultAccessGroup();
+ }
+ this.isLoadingAccessGroups = false;
+ this.changeDetectorRef.detectChanges();
+ },
+ error: () => {
+ this.setDefaultAccessGroup();
+ this.isLoadingAccessGroups = false;
+ this.changeDetectorRef.detectChanges();
+ }
});
this.unsubscribeService.add(accessGroupSubscription);
@@ -189,6 +201,12 @@ export class NewHashlistComponent implements OnInit, OnDestroy {
this.unsubscribeService.add(hashtypesSubscription$);
}
+ private setDefaultAccessGroup(): void {
+ this.selectAccessgroup = [{ id: '1', name: 'Default' }];
+ this.form.patchValue({ accessGroupId: 1 });
+ this.form.get('accessGroupId').disable();
+ }
+
get sourceType() {
return this.form.get('sourceType').value;
}