From bef51055a849aa420be12ec350e619b4a77565eb Mon Sep 17 00:00:00 2001 From: cv5ch <176032962+cv5ch@users.noreply.github.com> Date: Tue, 8 Jul 2025 17:45:18 +0200 Subject: [PATCH] Fixed issue not being able to access cracked hashes of supertask --- .../row-action-menu.constants.ts | 4 +- .../tables/base-table/base-table.component.ts | 41 ++++++++++++++++++- .../chunks-table/chunks-table.component.ts | 2 +- .../tables/ht-table/ht-table.component.html | 2 +- .../tables/ht-table/ht-table.component.ts | 7 ++++ .../link/ht-table-type-link.component.html | 31 ++++++++------ .../type/link/ht-table-type-link.component.ts | 34 ++++++++++++++- .../tasks-chunks-table.component.ts | 2 +- .../tasks-supertasks-table.component.html | 3 +- .../tasks-supertasks-table.component.ts | 13 ++++-- .../tasks-table/tasks-table.component.ts | 20 +-------- .../core/_datasources/hashes.datasource.ts | 22 +++++----- .../tasks-supertasks.datasource.ts | 8 +--- .../context-menu/tasks/task-menu.service.ts | 4 +- .../tasks/task-supertask-menu.service.ts | 2 +- .../modal-subtasks.component.html | 2 +- .../modal-subtasks.component.ts | 15 ++++--- 17 files changed, 141 insertions(+), 71 deletions(-) diff --git a/src/app/core/_components/menus/row-action-menu/row-action-menu.constants.ts b/src/app/core/_components/menus/row-action-menu/row-action-menu.constants.ts index f97d3cf3f..195b9c08b 100644 --- a/src/app/core/_components/menus/row-action-menu/row-action-menu.constants.ts +++ b/src/app/core/_components/menus/row-action-menu/row-action-menu.constants.ts @@ -59,7 +59,8 @@ export const RowActionMenuLabel = { REMOVE_ACCESSGROUP_AGENT: 'Remove Agent', REMOVE_ACCESSGROUP_USER: 'Remove User', ARCHIVE_PRETASK: 'Archive PreTask', - UNARCHIVE_PRETASK: 'Unarchive PreTask' + UNARCHIVE_PRETASK: 'Unarchive PreTask', + SHOW_SUBTASK: 'Show Subtasks' }; export const RowActionMenuAction = { @@ -72,6 +73,7 @@ export const RowActionMenuAction = { COPY_TO_PRETASK: 'copy-to-pretask', EDIT_TASKS: 'edit-tasks', EDIT_SUBTASKS: 'edit-subtasks', + SHOW_SUBTASKS: 'show-subtasks', NEW: 'new', IMPORT: 'import', EXPORT: 'export', diff --git a/src/app/core/_components/tables/base-table/base-table.component.ts b/src/app/core/_components/tables/base-table/base-table.component.ts index bef97809f..9f0fc658f 100644 --- a/src/app/core/_components/tables/base-table/base-table.component.ts +++ b/src/app/core/_components/tables/base-table/base-table.component.ts @@ -15,6 +15,7 @@ import { UIConfig, uiConfigDefault } from '@models/config-ui.model'; import { JHashlist } from '@models/hashlist.model'; import { JNotification } from '@models/notification.model'; import { JSuperTask } from '@models/supertask.model'; +import { JTask, JTaskWrapper, TaskType } from '@models/task.model'; import { JUser } from '@models/user.model'; import { ContextMenuService } from '@services/context-menu/base/context-menu.service'; @@ -111,7 +112,7 @@ export class BaseTableComponent { * @param chunk - chunk object to render router link for * @return observable object containing a router link array */ - renderCrackedLink(chunk: JChunk): Observable { + renderCrackedLinkFromChunk(chunk: JChunk): Observable { const links: HTTableRouterLink[] = []; if (chunk) { links.push({ @@ -122,6 +123,44 @@ export class BaseTableComponent { return of(links); } + /** + * Render cracked link from task object to be displayed in HTML code + * @return observable object containing a router link array + * @param task + */ + renderCrackedLinkFromTask(task: JTask): Observable { + const links: HTTableRouterLink[] = []; + if (task.chunkData.cracked) { + links.push({ + routerLink: ['/hashlists', 'hashes', 'tasks', task.id], + label: task.chunkData.cracked.toLocaleString() + }); + } + return of(links); + } + + /** + * Render router link to show cracked hashes for a task if any. + * For supertasks only the cracked number as text is shown + * @param wrapper - the task wrapper object to render the link for + * @return observable containing an array of router links to be rendered in HTML + */ + protected renderCrackedLinkFromWrapper(wrapper: JTaskWrapper): Observable { + if (wrapper.cracked === 0) { + return of([{ label: null, routerLink: null }]); + } + + const isSupertask = wrapper.taskType === TaskType.SUPERTASK; + + const link: HTTableRouterLink = { + label: wrapper.cracked.toLocaleString(), + routerLink: isSupertask ? null : ['/hashlists', 'hashes', 'tasks', wrapper.tasks[0].id], + tooltip: isSupertask ? 'Please access the cracked hashes via the row\'s context menu "show subtasks"' : undefined + }; + + return of([link]); + } + /** * Render hashlist link to be displayed in HTML code * @param hashlist - hashlist object to render router link for diff --git a/src/app/core/_components/tables/chunks-table/chunks-table.component.ts b/src/app/core/_components/tables/chunks-table/chunks-table.component.ts index bee193ffa..8ff2923f9 100644 --- a/src/app/core/_components/tables/chunks-table/chunks-table.component.ts +++ b/src/app/core/_components/tables/chunks-table/chunks-table.component.ts @@ -141,7 +141,7 @@ export class ChunksTableComponent extends BaseTableComponent implements OnInit { { id: ChunksTableCol.CRACKED, dataKey: 'cracked', - routerLink: (chunk: JChunk) => this.renderCrackedLink(chunk), + routerLink: (chunk: JChunk) => this.renderCrackedLinkFromChunk(chunk), isSortable: true } ]; diff --git a/src/app/core/_components/tables/ht-table/ht-table.component.html b/src/app/core/_components/tables/ht-table/ht-table.component.html index 4bd919b19..dd0e0dae4 100644 --- a/src/app/core/_components/tables/ht-table/ht-table.component.html +++ b/src/app/core/_components/tables/ht-table/ht-table.component.html @@ -220,7 +220,7 @@ } - + = new EventEmitter(); @Output() emitCopyRowData: EventEmitter = new EventEmitter(); @Output() emitFullHashModal: EventEmitter = new EventEmitter(); + @Output() linkClicked = new EventEmitter(); + /** Fetches user customizations */ private uiSettings: UISettingsUtilityClass; @@ -280,6 +282,11 @@ export class HTTableComponent implements OnInit, AfterViewInit, OnDestroy { this.loadingTimeoutSubscription.unsubscribe(); } } + + onLinkClicked() { + this.linkClicked.emit(); + } + copyRowDataEmit(event: JHash) { this.emitCopyRowData.emit(event); } diff --git a/src/app/core/_components/tables/ht-table/type/link/ht-table-type-link.component.html b/src/app/core/_components/tables/ht-table/type/link/ht-table-type-link.component.html index 5865efae4..95b4674ed 100644 --- a/src/app/core/_components/tables/ht-table/type/link/ht-table-type-link.component.html +++ b/src/app/core/_components/tables/ht-table/type/link/ht-table-type-link.component.html @@ -1,10 +1,10 @@ - + - + {{ link.label }} @@ -19,18 +19,23 @@ - - - {{ link.label }} - - - {{ element[tableColumn.dataKey] }} - - - - - +
+ + + {{ link.label }} + + + {{ element[tableColumn.dataKey] }} + + + + + +
+
+ + diff --git a/src/app/core/_components/tables/ht-table/type/link/ht-table-type-link.component.ts b/src/app/core/_components/tables/ht-table/type/link/ht-table-type-link.component.ts index f56e57638..083edf5d4 100644 --- a/src/app/core/_components/tables/ht-table/type/link/ht-table-type-link.component.ts +++ b/src/app/core/_components/tables/ht-table/type/link/ht-table-type-link.component.ts @@ -1,8 +1,8 @@ -import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; +import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; import { BaseModel } from '@models/base.model'; -import { HTTableColumn } from '@components/tables/ht-table/ht-table.models'; +import { HTTableColumn, HTTableRouterLink } from '@components/tables/ht-table/ht-table.models'; @Component({ selector: 'app-ht-table-link', @@ -13,4 +13,34 @@ import { HTTableColumn } from '@components/tables/ht-table/ht-table.models'; export class HTTableTypeLinkComponent { @Input() element: BaseModel; @Input() tableColumn: HTTableColumn; + + @Output() linkClicked = new EventEmitter(); + + onLinkClicked() { + this.linkClicked.emit(); + } + + /** + * TrackBy function for HTTableRouterLink items in an ngFor loop. + * + * This function helps Angular identify each item uniquely to optimize DOM updates + * by avoiding re-rendering of unchanged items. + * + * Since HTTableRouterLink does not have a unique `id`, this function uses the + * joined `routerLink` array as a unique key. If `routerLink` is empty, it falls back + * to the `label`. If neither is available, it returns the index as a last resort. + * + * @param index - The index of the item in the iterable. + * @param item - The HTTableRouterLink item. + * @returns A unique identifier for the item (string, number, or index). + */ + trackByFn(index: number, item: HTTableRouterLink): string | number { + if (item.routerLink && item.routerLink.length) { + return item.routerLink.join('-'); + } + if (item.label) { + return item.label; + } + return index; + } } diff --git a/src/app/core/_components/tables/tasks-chunks-table/tasks-chunks-table.component.ts b/src/app/core/_components/tables/tasks-chunks-table/tasks-chunks-table.component.ts index 206ee3bac..2641bbff6 100644 --- a/src/app/core/_components/tables/tasks-chunks-table/tasks-chunks-table.component.ts +++ b/src/app/core/_components/tables/tasks-chunks-table/tasks-chunks-table.component.ts @@ -133,7 +133,7 @@ export class TasksChunksTableComponent extends BaseTableComponent implements OnI { id: TasksChunksTableCol.CRACKED, dataKey: 'cracked', - routerLink: (chunk: JChunk) => this.renderCrackedLink(chunk), + routerLink: (chunk: JChunk) => this.renderCrackedLinkFromChunk(chunk), isSortable: true } ]; diff --git a/src/app/core/_components/tables/tasks-supertasks-table/tasks-supertasks-table.component.html b/src/app/core/_components/tables/tasks-supertasks-table/tasks-supertasks-table.component.html index ac73d0db6..ba8d044f6 100644 --- a/src/app/core/_components/tables/tasks-supertasks-table/tasks-supertasks-table.component.html +++ b/src/app/core/_components/tables/tasks-supertasks-table/tasks-supertasks-table.component.html @@ -6,13 +6,12 @@ [dataSource]="dataSource" [tableColumns]="tableColumns" [isSelectable]="true" - [contextMenuService]="contextMenuService" [isFilterable]="true" [filterFn]="filter" [isPageable]="true" (editableSaved)="editableSaved($event)" (bulkActionClicked)="bulkActionClicked($event)" - (rowActionClicked)="rowActionClicked($event)" (exportActionClicked)="exportActionClicked($event)" + (linkClicked)="onLinkClick()" /> diff --git a/src/app/core/_components/tables/tasks-supertasks-table/tasks-supertasks-table.component.ts b/src/app/core/_components/tables/tasks-supertasks-table/tasks-supertasks-table.component.ts index dafeb81d2..eede67e0f 100644 --- a/src/app/core/_components/tables/tasks-supertasks-table/tasks-supertasks-table.component.ts +++ b/src/app/core/_components/tables/tasks-supertasks-table/tasks-supertasks-table.component.ts @@ -1,6 +1,6 @@ import { catchError, forkJoin } from 'rxjs'; -import { Component, Input, OnDestroy, OnInit } from '@angular/core'; +import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core'; import { SafeHtml } from '@angular/platform-browser'; import { ChunkData } from '@models/chunk.model'; @@ -33,6 +33,7 @@ import { convertToLocale } from '@src/app/shared/utils/util'; }) export class TasksSupertasksTableComponent extends BaseTableComponent implements OnInit, OnDestroy { @Input() supertaskId = 0; + @Output() linkClicked = new EventEmitter(); tableColumns: HTTableColumn[] = []; dataSource: TasksSupertasksDataSource; @@ -55,6 +56,10 @@ export class TasksSupertasksTableComponent extends BaseTableComponent implements } } + onLinkClick(): void { + this.linkClicked.emit(); + } + filter(item: JTask, filterValue: string): boolean { return item.taskName.toLowerCase().includes(filterValue); } @@ -83,7 +88,7 @@ export class TasksSupertasksTableComponent extends BaseTableComponent implements { id: TasksSupertasksDataSourceTableCol.CRACKED, dataKey: 'cracked', - //routerLink: (wrapper: JTask) => this.renderCrackedLink(wrapper), + routerLink: (task: JTask) => this.renderCrackedLinkFromTask(task), isSortable: true }, { @@ -325,7 +330,7 @@ export class TasksSupertasksTableComponent extends BaseTableComponent implements let val = 0; try { val = parseInt(priority); - } catch (error) { + } catch { // Do nothing } @@ -357,7 +362,7 @@ export class TasksSupertasksTableComponent extends BaseTableComponent implements let val = 0; try { val = parseInt(max); - } catch (error) { + } catch { // Do nothing } diff --git a/src/app/core/_components/tables/tasks-table/tasks-table.component.ts b/src/app/core/_components/tables/tasks-table/tasks-table.component.ts index d103356a0..e52a20aa0 100644 --- a/src/app/core/_components/tables/tasks-table/tasks-table.component.ts +++ b/src/app/core/_components/tables/tasks-table/tasks-table.component.ts @@ -295,7 +295,7 @@ export class TasksTableComponent extends BaseTableComponent implements OnInit, O case RowActionMenuAction.EDIT_TASKS: this.rowActionEdit(event.data); break; - case RowActionMenuAction.EDIT_SUBTASKS: + case RowActionMenuAction.SHOW_SUBTASKS: // eslint-disable-next-line no-case-declarations this.rowActionEditSubtasks(event.data); break; @@ -737,24 +737,6 @@ export class TasksTableComponent extends BaseTableComponent implements OnInit, O ); } - /** - * Render router link to show cracked hashes for a task if any. - * For supertasks only the cracked number as text is shown - * @param wrapper - the task wrapper object to render the link for - * @return observable containing an array of router links to be rendered in HTML - * @private - */ - private renderCrackedLinkFromWrapper(wrapper: JTaskWrapper): Observable { - if (wrapper.cracked === 0) { - return of([{ label: null, routerLink: null }]); - } - const link: HTTableRouterLink = { - label: wrapper.cracked.toLocaleString(), - routerLink: wrapper.taskType === TaskType.TASK ? ['/hashlists', 'hashes', 'tasks', wrapper.tasks[0].id] : null - }; - return of([link]); - } - /** * Render router links for any type of tasks for a task wrapper object * @param wrapper - the task wrapper object to render the link for diff --git a/src/app/core/_datasources/hashes.datasource.ts b/src/app/core/_datasources/hashes.datasource.ts index 4ea13b28f..dbde00f99 100644 --- a/src/app/core/_datasources/hashes.datasource.ts +++ b/src/app/core/_datasources/hashes.datasource.ts @@ -1,13 +1,15 @@ import { catchError, finalize, of } from 'rxjs'; -import { BaseDataSource } from '@datasources/base.datasource'; -import { FilterType } from '@models/request-params.model'; import { JHash } from '@models/hash.model'; -import { JsonAPISerializer } from '@services/api/serializer-service'; -import { RequestParamBuilder } from '@services/params/builder-implementation.service'; +import { FilterType } from '@models/request-params.model'; import { ResponseWrapper } from '@models/response.model'; +import { JTask } from '@models/task.model'; + +import { JsonAPISerializer } from '@services/api/serializer-service'; import { SERV } from '@services/main.config'; -import { JTaskWrapper } from '@models/task.model'; +import { RequestParamBuilder } from '@services/params/builder-implementation.service'; + +import { BaseDataSource } from '@datasources/base.datasource'; export class HashesDataSource extends BaseDataSource { private _id = 0; @@ -25,23 +27,23 @@ export class HashesDataSource extends BaseDataSource { this.loading = true; if (this._dataType === 'tasks') { - const paramsTaskwrapper = new RequestParamBuilder().addInitial(this).addInclude('tasks'); + const paramsTasks = new RequestParamBuilder().addInitial(this).addInclude('hashlist'); - const taskwrapperService = this.service.get(SERV.TASKS_WRAPPER, this._id, paramsTaskwrapper.create()); + const taskService = this.service.get(SERV.TASKS, this._id, paramsTasks.create()); this.subscriptions.push( - taskwrapperService + taskService .pipe( catchError(() => of([])), finalize(() => (this.loading = false)) ) .subscribe((response: ResponseWrapper) => { - const taskwrapper = new JsonAPISerializer().deserialize({ + const task = new JsonAPISerializer().deserialize({ data: response.data, included: response.included }); - const hashlistId = taskwrapper.hashlistId; + const hashlistId = task.hashlist.id; const paramsHashlist = new RequestParamBuilder() .addInitial(this) diff --git a/src/app/core/_datasources/tasks-supertasks.datasource.ts b/src/app/core/_datasources/tasks-supertasks.datasource.ts index 2d3639b00..28c980bce 100644 --- a/src/app/core/_datasources/tasks-supertasks.datasource.ts +++ b/src/app/core/_datasources/tasks-supertasks.datasource.ts @@ -42,13 +42,7 @@ export class TasksSupertasksDataSource extends BaseDataSource { }); const length = response.meta.page.total_elements; - this.setPaginationConfig( - this.pageSize, - length, - this.pageAfter, - this.pageBefore, - this.index - ); + this.setPaginationConfig(this.pageSize, length, this.pageAfter, this.pageBefore, this.index); const subtasks = taskWrappers[0].tasks; const chunkParams = new RequestParamBuilder().addFilter({ diff --git a/src/app/core/_services/context-menu/tasks/task-menu.service.ts b/src/app/core/_services/context-menu/tasks/task-menu.service.ts index 31854d41c..6f5ffae9e 100644 --- a/src/app/core/_services/context-menu/tasks/task-menu.service.ts +++ b/src/app/core/_services/context-menu/tasks/task-menu.service.ts @@ -27,8 +27,8 @@ export class TaskContextMenuService extends ContextMenuService { this.addCtxEditItem(RowActionMenuLabel.EDIT_TASK, RowActionMenuAction.EDIT_TASKS, permTaskUpdate, isTaskCondition); this.addCtxEditItem( - RowActionMenuLabel.EDIT_SUBTASKS, - RowActionMenuAction.EDIT_SUBTASKS, + RowActionMenuLabel.SHOW_SUBTASK, + RowActionMenuAction.SHOW_SUBTASKS, permTaskUpdate, isSuperTaskCondition ); diff --git a/src/app/core/_services/context-menu/tasks/task-supertask-menu.service.ts b/src/app/core/_services/context-menu/tasks/task-supertask-menu.service.ts index 589d75cb6..844bf370b 100644 --- a/src/app/core/_services/context-menu/tasks/task-supertask-menu.service.ts +++ b/src/app/core/_services/context-menu/tasks/task-supertask-menu.service.ts @@ -20,7 +20,7 @@ export class TaskSuperTaskContextMenuService extends PreTaskContextMenuService { const isArchiveCondition: ContextMenuCondition = { key: 'isArchived', value: false }; const isUnArchiveCondition: ContextMenuCondition = { key: 'isArchived', value: true }; - this.addCtxArchiveItem(RowActionMenuLabel.ARCHIVE_PRETASK, permUpdate, isArchiveCondition); + this.addCtxArchiveItem(RowActionMenuLabel.ARCHIVE_TASK, permUpdate, isArchiveCondition); this.addCtxUnArchiveItem(RowActionMenuLabel.UNARCHIVE_PRETASK, permUpdate, isUnArchiveCondition); this.addBulkArchiveItem(BulkActionMenuLabel.ARCHIVE_PRETASKS, permUpdate); diff --git a/src/app/tasks/show-tasks/modal-subtasks/modal-subtasks.component.html b/src/app/tasks/show-tasks/modal-subtasks/modal-subtasks.component.html index 65bf4445a..23b1dd9ec 100644 --- a/src/app/tasks/show-tasks/modal-subtasks/modal-subtasks.component.html +++ b/src/app/tasks/show-tasks/modal-subtasks/modal-subtasks.component.html @@ -1,4 +1,4 @@ - + diff --git a/src/app/tasks/show-tasks/modal-subtasks/modal-subtasks.component.ts b/src/app/tasks/show-tasks/modal-subtasks/modal-subtasks.component.ts index 0e1ca566b..289297bc7 100644 --- a/src/app/tasks/show-tasks/modal-subtasks/modal-subtasks.component.ts +++ b/src/app/tasks/show-tasks/modal-subtasks/modal-subtasks.component.ts @@ -1,15 +1,16 @@ import { Component, Inject } from '@angular/core'; -import { MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; @Component({ - selector: 'app-modal-subtasks', - templateUrl: './modal-subtasks.component.html', - standalone: false + selector: 'app-modal-subtasks', + templateUrl: './modal-subtasks.component.html', + standalone: false }) export class ModalSubtasksComponent { constructor( @Inject(MAT_DIALOG_DATA) - public data: { supertaskId: number; supertaskName: string } + public data: { supertaskId: number; supertaskName: string }, + private dialogRef: MatDialogRef ) {} get supertaskId(): number { @@ -19,4 +20,8 @@ export class ModalSubtasksComponent { get supertaskName(): string { return 'Subtasks of ' + this.data.supertaskName; } + + close(): void { + this.dialogRef.close(); + } }